diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f4ea099d9c8..ebf6f614df3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "dockerfile": "Dockerfile", "args": { // Update the VARIANT arg to pick a version of Go - "VARIANT": "1.19", + "VARIANT": "1.20", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*" diff --git a/.github/workflows/adapter-code-coverage.yml b/.github/workflows/adapter-code-coverage.yml new file mode 100644 index 00000000000..b4c3f0745d6 --- /dev/null +++ b/.github/workflows/adapter-code-coverage.yml @@ -0,0 +1,107 @@ +name: Adapter code coverage +on: + pull_request_target: + paths: ["adapters/*/*.go"] +permissions: + pull-requests: write + contents: write +jobs: + run-coverage: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.20.5 + + - name: Checkout pull request branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Get adapter directories + id: get_directories + uses: actions/github-script@v6 + with: + result-encoding: string + script: | + const utils = require('./.github/workflows/helpers/pull-request-utils.js') + function directoryExtractor(filepath, status) { + // extract directory name only if file is not removed and file is in adapters directory + if (status != "removed" && filepath.startsWith("adapters/") && filepath.split("/").length > 2) { + return filepath.split("/")[1] + } + return "" + } + const helper = utils.diffHelper({github, context}) + const files = await helper.getDirectories(directoryExtractor) + return files.length == 0 ? "" : JSON.stringify(files); + + - name: Run coverage tests + id: run_coverage + if: steps.get_directories.outputs.result != '' + run: | + directories=$(echo '${{ steps.get_directories.outputs.result }}' | jq -r '.[]') + go mod download + + # create a temporary directory to store the coverage output + temp_dir=$(mktemp -d) + touch ${temp_dir}/coverage_output.txt + + # generate coverage for adapter + cd ./adapters + for directory in $directories; do + cd $directory + coverage_profile_path="${PWD}/${directory}.out" + go test -coverprofile="${coverage_profile_path}" + go tool cover -html="${coverage_profile_path}" -o "${temp_dir}/${directory}.html" + go tool cover -func="${coverage_profile_path}" -o "${temp_dir}/${directory}.txt" + cd .. + done + echo "coverage_dir=${temp_dir}" >> $GITHUB_OUTPUT + + # remove pull request branch files + cd .. + rm -f -r ./* + + - name: Checkout coverage-preview branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: coverage-preview + repository: prebid/prebid-server + + - name: Commit coverage files to coverage-preview branch + if: steps.run_coverage.outputs.coverage_dir != '' + id: commit_coverage + run: | + directory=.github/preview/${{ github.run_id }}_$(date +%s) + mkdir -p $directory + cp -r ${{ steps.run_coverage.outputs.coverage_dir }}/*.html ./$directory + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add $directory/* + git commit -m 'Add coverage files' + git push origin coverage-preview + echo "remote_coverage_preview_dir=${directory}" >> $GITHUB_OUTPUT + + - name: Checkout master branch + if: steps.get_directories.outputs.result != '' + run: git checkout master + + - name: Add coverage summary to pull request + if: steps.run_coverage.outputs.coverage_dir != '' && steps.commit_coverage.outputs.remote_coverage_preview_dir != '' + uses: actions/github-script@v6 + with: + script: | + const utils = require('./.github/workflows/helpers/pull-request-utils.js') + const helper = utils.coverageHelper({ + github, context, + headSha: '${{ github.event.pull_request.head.sha }}', + tmpCoverageDir: '${{ steps.run_coverage.outputs.coverage_dir }}', + remoteCoverageDir: '${{ steps.commit_coverage.outputs.remote_coverage_preview_dir }}' + }) + const adapterDirectories = JSON.parse('${{ steps.get_directories.outputs.result }}') + await helper.AddCoverageSummary(adapterDirectories) diff --git a/.github/workflows/helpers/pull-request-utils.js b/.github/workflows/helpers/pull-request-utils.js new file mode 100644 index 00000000000..941ae5b6953 --- /dev/null +++ b/.github/workflows/helpers/pull-request-utils.js @@ -0,0 +1,414 @@ +const synchronizeEvent = "synchronize", + openedEvent = "opened", + completedStatus = "completed", + resultSize = 100 + +class diffHelper { + constructor(input) { + this.owner = input.context.repo.owner + this.repo = input.context.repo.repo + this.github = input.github + this.pullRequestNumber = input.context.payload.pull_request.number + this.pullRequestEvent = input.event + this.testName = input.testName + this.fileNameFilter = !input.fileNameFilter ? () => true : input.fileNameFilter + this.fileLineFilter = !input.fileLineFilter ? () => true : input.fileLineFilter + } + + /* + Checks whether the test defined by this.testName has been executed on the given commit + @param {string} commit - commit SHA to check for test execution + @returns {boolean} - returns true if the test has been executed on the commit, otherwise false + */ + async #isTestExecutedOnCommit(commit) { + const response = await this.github.rest.checks.listForRef({ + owner: this.owner, + repo: this.repo, + ref: commit, + }) + + return response.data.check_runs.some( + ({ status, name }) => status === completedStatus && name === this.testName + ) + } + + /* + Retrieves the line numbers of added or updated lines in the provided files + @param {Array} files - array of files containing their filename and patch + @returns {Object} - object mapping filenames to arrays of line numbers indicating the added or updated lines + */ + async #getDiffForFiles(files = []) { + let diff = {} + for (const { filename, patch } of files) { + if (this.fileNameFilter(filename)) { + const lines = patch.split("\n") + if (lines.length === 1) { + continue + } + + let lineNumber + for (const line of lines) { + // Check if line is diff header + // example: + // @@ -1,3 +1,3 @@ + // 1 var a + // 2 + // 3 - //test + // 3 +var b + // Here @@ -1,3 +1,3 @@ is diff header + if (line.match(/@@\s.*?@@/) != null) { + lineNumber = parseInt(line.match(/\+(\d+)/)[0]) + continue + } + + // "-" prefix indicates line was deleted. So do not consider deleted line + if (line.startsWith("-")) { + continue + } + + // "+"" prefix indicates line was added or updated. Include line number in diff details + if (line.startsWith("+") && this.fileLineFilter(line)) { + diff[filename] = diff[filename] || [] + diff[filename].push(lineNumber) + } + lineNumber++ + } + } + } + return diff + } + + /* + Retrieves a list of commits that have not been checked by the test defined by this.testName + @returns {Array} - array of commit SHAs that have not been checked by the test + */ + async #getNonScannedCommits() { + const { data } = await this.github.rest.pulls.listCommits({ + owner: this.owner, + repo: this.repo, + pull_number: this.pullRequestNumber, + per_page: resultSize, + }) + let nonScannedCommits = [] + + // API returns commits in ascending order. Loop in reverse to quickly retrieve unchecked commits + for (let i = data.length - 1; i >= 0; i--) { + const { sha, parents } = data[i] + + // Commit can be merged master commit. Such commit have multiple parents + // Do not consider such commit for building file diff + if (parents.length > 1) { + continue + } + + const isTestExecuted = await this.#isTestExecutedOnCommit(sha) + if (isTestExecuted) { + // Remaining commits have been tested in previous scans. Therefore, do not need to be considered again + break + } else { + nonScannedCommits.push(sha) + } + } + + // Reverse to return commits in ascending order. This is needed to build diff for commits in chronological order + return nonScannedCommits.reverse() + } + + /* + Filters the commit diff to include only the files that are part of the PR diff + @param {Array} commitDiff - array of line numbers representing lines added or updated in the commit + @param {Array} prDiff - array of line numbers representing lines added or updated in the pull request + @returns {Array} - filtered commit diff, including only the files that are part of the PR diff + */ + async #filterCommitDiff(commitDiff = [], prDiff = []) { + return commitDiff.filter((file) => prDiff.includes(file)) + } + + /* + Builds the diff for the pull request, including both the changes in the pull request and the changes in non-scanned commits + @returns {string} - json string representation of the pull request diff and the diff for non-scanned commits + */ + async buildDiff() { + const { data } = await this.github.rest.pulls.listFiles({ + owner: this.owner, + repo: this.repo, + pull_number: this.pullRequestNumber, + per_page: resultSize, + }) + + const pullRequestDiff = await this.#getDiffForFiles(data) + + const nonScannedCommitsDiff = + Object.keys(pullRequestDiff).length != 0 && this.pullRequestEvent === synchronizeEvent // The "synchronize" event implies that new commit are pushed after the pull request was opened + ? await this.getNonScannedCommitDiff(pullRequestDiff) + : {} + + const prDiffFiles = Object.keys(pullRequestDiff) + const pullRequest = { + hasChanges: prDiffFiles.length > 0, + files: prDiffFiles.join(" "), + diff: pullRequestDiff, + } + const uncheckedCommits = { diff: nonScannedCommitsDiff } + return JSON.stringify({ pullRequest, uncheckedCommits }) + } + + /* + Retrieves the diff for non-scanned commits by comparing their changes with the pull request diff + @param {Object} pullRequestDiff - The diff of files in the pull request + @returns {Object} - The diff of files in the non-scanned commits that are part of the pull request diff + */ + async getNonScannedCommitDiff(pullRequestDiff) { + let nonScannedCommitsDiff = {} + // Retrieves list of commits that have not been scanned by the PR check + const nonScannedCommits = await this.#getNonScannedCommits() + for (const commit of nonScannedCommits) { + const { data } = await this.github.rest.repos.getCommit({ + owner: this.owner, + repo: this.repo, + ref: commit, + }) + + const commitDiff = await this.#getDiffForFiles(data.files) + const files = Object.keys(commitDiff) + for (const file of files) { + // Consider scenario where the changes made to a file in the initial commit are completely undone by subsequent commits + // In such cases, the modifications from the initial commit should not be taken into account + // If the changes were entirely removed, there should be no entry for the file in the pullRequestStats + const filePRDiff = pullRequestDiff[file] + if (!filePRDiff) { + continue + } + + // Consider scenario where changes made in the commit were partially removed or modified by subsequent commits + // In such cases, include only those commit changes that are part of the pullRequestStats object + // This ensures that only the changes that are reflected in the pull request are considered + const changes = await this.#filterCommitDiff(commitDiff[file], filePRDiff) + + if (changes.length !== 0) { + // Check if nonScannedCommitsDiff[file] exists, if not assign an empty array to it + nonScannedCommitsDiff[file] = nonScannedCommitsDiff[file] || [] + // Combine the existing nonScannedCommitsDiff[file] array with the commit changes + // Remove any duplicate elements using the Set data structure + nonScannedCommitsDiff[file] = [ + ...new Set([...nonScannedCommitsDiff[file], ...changes]), + ] + } + } + } + return nonScannedCommitsDiff + } + + /* + Retrieves a list of directories from GitHub pull request files + @param {Function} directoryExtractor - The function used to extract the directory name from the filename + @returns {Array} An array of unique directory names + */ + async getDirectories(directoryExtractor = () => "") { + const { data } = await this.github.rest.pulls.listFiles({ + owner: this.owner, + repo: this.repo, + pull_number: this.pullRequestNumber, + per_page: resultSize, + }) + + const directories = [] + for (const { filename, status } of data) { + const directory = directoryExtractor(filename, status) + if (directory != "" && !directories.includes(directory)) { + directories.push(directory) + } + } + return directories + } +} + +class semgrepHelper { + constructor(input) { + this.owner = input.context.repo.owner + this.repo = input.context.repo.repo + this.github = input.github + + this.pullRequestNumber = input.context.payload.pull_request.number + this.pullRequestEvent = input.event + + this.pullRequestDiff = input.diff.pullRequest.diff + this.newCommitsDiff = input.diff.uncheckedCommits.diff + + this.semgrepErrors = [] + this.semgrepWarnings = [] + input.semgrepResult.forEach((res) => { + res.severity === "High" ? this.semgrepErrors.push(res) : this.semgrepWarnings.push(res) + }) + + this.headSha = input.headSha + } + + /* + Retrieves the matching line number from the provided diff for a given file and range of lines + @param {Object} range - object containing the file, start line, and end line to find a match + @param {Object} diff - object containing file changes and corresponding line numbers + @returns {number|null} - line number that matches the range within the diff, or null if no match is found + */ + async #getMatchingLineFromDiff({ file, start, end }, diff) { + const fileDiff = diff[file] + if (!fileDiff) { + return null + } + if (fileDiff.includes(start)) { + return start + } + if (fileDiff.includes(end)) { + return end + } + return null + } + + /* + Splits the semgrep results into different categories based on the scan + @param {Array} semgrepResults - array of results reported by semgrep + @returns {Object} - object containing the categorized semgrep results i.e results reported in previous scans and new results found in the current scan + */ + async #splitSemgrepResultsByScan(semgrepResults = []) { + const result = { + nonDiff: [], // Errors or warnings found in files updated in pull request, but not part of sections that were modified in the pull request + previous: [], // Errors or warnings found in previous semgrep scans + current: [], // Errors or warnings found in current semgrep scan + } + + for (const se of semgrepResults) { + const prDiffLine = await this.#getMatchingLineFromDiff(se, this.pullRequestDiff) + if (!prDiffLine) { + result.nonDiff.push({ ...se }) + continue + } + + switch (this.pullRequestEvent) { + case openedEvent: + // "Opened" event implies that this is the first check + // Therefore, the error should be appended to the result.current + result.current.push({ ...se, line: prDiffLine }) + case synchronizeEvent: + const commitDiffLine = await this.#getMatchingLineFromDiff(se, this.newCommitsDiff) + // Check if error or warning is part of current commit diff + // If not then error or warning was reported in previous scans + commitDiffLine != null + ? result.current.push({ ...se, line: commitDiffLine }) + : result.previous.push({ + ...se, + line: prDiffLine, + }) + } + } + return result + } + + /* + Adds review comments based on the semgrep results to the current pull request + @returns {Object} - object containing the count of unaddressed comments from the previous scan and the count of new comments from the current scan + */ + async addReviewComments() { + let result = { + previousScan: { unAddressedComments: 0 }, + currentScan: { newComments: 0 }, + } + + if (this.semgrepErrors.length == 0 && this.semgrepWarnings.length == 0) { + return result + } + + const errors = await this.#splitSemgrepResultsByScan(this.semgrepErrors) + if (errors.previous.length == 0 && errors.current.length == 0) { + console.log("Semgrep did not find any errors in the current pull request changes") + } else { + for (const { message, file, line } of errors.current) { + await this.github.rest.pulls.createReviewComment({ + owner: this.owner, + repo: this.repo, + pull_number: this.pullRequestNumber, + commit_id: this.headSha, + body: message, + path: file, + line: line, + }) + } + result.currentScan.newComments = errors.current.length + if (this.pullRequestEvent == synchronizeEvent) { + result.previousScan.unAddressedComments = errors.previous.length + } + } + + const warnings = await this.#splitSemgrepResultsByScan(this.semgrepWarnings) + for (const { message, file, line } of warnings.current) { + await this.github.rest.pulls.createReviewComment({ + owner: this.owner, + repo: this.repo, + pull_number: this.pullRequestNumber, + commit_id: this.headSha, + body: "Consider this as a suggestion. " + message, + path: file, + line: line, + }) + } + return result + } +} + +class coverageHelper { + constructor(input) { + this.owner = input.context.repo.owner + this.repo = input.context.repo.repo + this.github = input.github + this.pullRequestNumber = input.context.payload.pull_request.number + this.headSha = input.headSha + this.previewBaseURL = `https://htmlpreview.github.io/?https://github.com/${this.owner}/${this.repo}/coverage-preview/${input.remoteCoverageDir}` + this.tmpCoverDir = input.tmpCoverageDir + } + + /* + Adds a code coverage summary along with heatmap links and coverage data on pull request as comment + @param {Array} directories - directory for which coverage summary will be added + */ + async AddCoverageSummary(directories = []) { + const fs = require("fs") + const path = require("path") + const { promisify } = require("util") + const readFileAsync = promisify(fs.readFile) + + let body = "## Code coverage summary \n" + body += "Note: \n" + body += + "- Prebid team doesn't anticipate tests covering code paths that might result in marshal and unmarshal errors \n" + body += `- Coverage summary encompasses all commits leading up to the latest one, ${this.headSha} \n` + + for (const directory of directories) { + let url = `${this.previewBaseURL}/${directory}.html` + try { + const textFilePath = path.join(this.tmpCoverDir, `${directory}.txt`) + const data = await readFileAsync(textFilePath, "utf8") + + body += `#### ${directory} \n` + body += `Refer [here](${url}) for heat map coverage report \n` + body += "\`\`\` \n" + body += data + body += "\n \`\`\` \n" + } catch (err) { + console.error(err) + return + } + } + + await this.github.rest.issues.createComment({ + owner: this.owner, + repo: this.repo, + issue_number: this.pullRequestNumber, + body: body, + }) + } +} + +module.exports = { + diffHelper: (input) => new diffHelper(input), + semgrepHelper: (input) => new semgrepHelper(input), + coverageHelper: (input) => new coverageHelper(input), +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ea29bd3d61..f1d2b10c41c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,11 +6,12 @@ on: releaseType: type: choice options: + - major - minor - patch default: minor required: true - description: 'minor: v0.X.0, patch: v0.0.X' + description: 'major: vX.0.0, minor: v0.X.0, patch: v0.0.X' debug: type: boolean default: true @@ -32,9 +33,25 @@ jobs: outputs: hasWritePermission: ${{ steps.check.outputs.require-result }} + build-master: + name: Build master + needs: check-permission + if: contains(needs.check-permission.outputs.hasWritePermission, 'true') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + with: + fetch-depth: 0 + repository: ${{ github.repository }} + ref: master + - name: Build and validate + run: | + ./validate.sh + publish-tag: name: Publish tag - needs: check-permission + needs: build-master if: contains(needs.check-permission.outputs.hasWritePermission, 'true') permissions: contents: write @@ -62,11 +79,18 @@ jobs: nextTag='' releaseType=${{ inputs.releaseType }} - if [ $releaseType == "minor" ]; then - # increment minor version and reset patch version - nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $2+=1; $3=0; print $0}') + if [ $releaseType == "major" ]; then + # PBS-GO skipped the v1.0.0 major release - https://github.com/prebid/prebid-server/issues/3068 + # If the current tag is v0.x.x, the script sets the next release tag to v2.0.0 + # Otherwise, the script increments the major version by 1 and sets the minor and patch versions to zero + # For example, v2.x.x will be incremented to v3.0.0 + major=$(echo "${currentTag}" | awk -F. '{gsub(/^v/, "", $1); if($1 == 0) $1=2; else $1+=1; print $1}') + nextTag="v${major}.0.0" + elif [ $releaseType == "minor" ]; then + # Increment minor version and reset patch version + nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $2+=1; $3=0; print $0}') else - # increment patch version + # Increment patch version nextTag=$(echo "${currentTag}" | awk -F. '{OFS="."; $3+=1; print $0}') fi diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000000..565bb9b871a --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,77 @@ +name: Adapter semgrep checks +on: + pull_request_target: + paths: ["adapters/*/*.go"] +permissions: + pull-requests: write +jobs: + semgrep-check: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Calculate diff + id: calculate_diff + uses: actions/github-script@v6 + with: + result-encoding: string + script: | + const utils = require('./.github/workflows/helpers/pull-request-utils.js') + // consider only non-test Go files that are part of the adapter code + function fileNameFilter(filename) { + return filename.startsWith("adapters/") && filename.split("/").length > 2 && filename.endsWith(".go") && !filename.endsWith("_test.go") + } + const helper = utils.diffHelper({github, context, fileNameFilter, event: "${{github.event.action}}", testName: "${{github.job}}"}) + return await helper.buildDiff() + + - name: Should run semgrep + id: should_run_semgrep + run: | + hasChanges=$(echo '${{ steps.calculate_diff.outputs.result }}' | jq .pullRequest.hasChanges) + echo "hasChanges=${hasChanges}" >> $GITHUB_OUTPUT + + - name: Install semgrep + if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') + run: | + pip3 install semgrep==1.22.0 + semgrep --version + + - name: Run semgrep tests + id: run_semgrep_tests + if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') + run: | + unqouted_string=$(echo '${{ steps.calculate_diff.outputs.result }}' | jq .pullRequest.files | tr -d '"') + outputs=$(semgrep --gitlab-sast --config=.semgrep/adapter $unqouted_string | jq '[.vulnerabilities[] | {"file": .location.file, "severity": .severity, "start": .location.start_line, "end": .location.end_line, "message": (.message | gsub("\\n"; "\n"))}]' | jq -c | jq -R) + echo "semgrep_result=${outputs}" >> "$GITHUB_OUTPUT" + + - name: Add pull request comment + id: add_pull_request_comment + if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') + uses: actions/github-script@v6.4.1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + const utils = require('./.github/workflows/helpers/pull-request-utils.js') + const helper = utils.semgrepHelper({ + github, context, event: "${{github.event.action}}", + semgrepResult: JSON.parse(${{ steps.run_semgrep_tests.outputs.semgrep_result }}), + diff: ${{ steps.calculate_diff.outputs.result }}, headSha: "${{github.event.pull_request.head.sha}}" + }) + const { previousScan, currentScan } = await helper.addReviewComments() + return previousScan.unAddressedComments + currentScan.newComments + + - name: Adapter semgrep checks result + if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') + run: | + if [ "${{steps.add_pull_request_comment.outputs.result}}" -ne "0" ]; then + echo 'Semgrep has found "${{steps.add_pull_request_comment.outputs.result}}" errors' + exit 1 + else + echo 'Semgrep did not find any errors in the pull request changes' + fi diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index e85dc3de50c..07f1bacaa45 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -12,7 +12,7 @@ jobs: - name: Install Go uses: actions/setup-go@v4 with: - go-version: 1.19.2 + go-version: 1.20.5 - name: Checkout Merged Branch uses: actions/checkout@v3 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 83b29709bd4..9047e1f468f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,7 +10,7 @@ jobs: validate: strategy: matrix: - go-version: [1.18.x, 1.19.x] + go-version: [1.19.x, 1.20.x] os: [ubuntu-20.04] runs-on: ${{ matrix.os }} diff --git a/.gitignore b/.gitignore index 4e298f2a3d9..0df7cde54fd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,8 @@ vendor # build artifacts prebid-server -build -debug +/build +/debug __debug_bin # config files diff --git a/.semgrep/README.md b/.semgrep/README.md new file mode 100644 index 00000000000..cd19e4bcc61 --- /dev/null +++ b/.semgrep/README.md @@ -0,0 +1,17 @@ +# Semgrep Test + +Running semgrep unit tests: +```bash +semgrep --test +``` + + +Running single semgrep rules against adapter code: +```bash +semgrep --config=./adapter/{rule}.yml ../adapters/ +``` + +Running all semgrep rules simultaneously: +```bash +semgrep --config=./adapter ../adapters/ +``` diff --git a/.semgrep/adapter/bid-type-if-check.go b/.semgrep/adapter/bid-type-if-check.go new file mode 100644 index 00000000000..52b5d551377 --- /dev/null +++ b/.semgrep/adapter/bid-type-if-check.go @@ -0,0 +1,55 @@ +/* + bid-type-if-check tests + https://semgrep.dev/docs/writing-rules/testing-rules + "ruleid" prefix in comment indicates patterns that should be flagged by semgrep + "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep +*/ + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID { + // ruleid: bid-type-if-check + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + // ruleid: bid-type-if-check + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + // ruleid: bid-type-if-check + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + // ruleid: bid-type-if-check + } else if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } + } + } + return openrtb_ext.BidTypeBanner +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + // ruleid: bid-type-if-check + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), + } +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + // ruleid: bid-type-if-check + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), + } +} diff --git a/.semgrep/adapter/bid-type-if-check.yml b/.semgrep/adapter/bid-type-if-check.yml new file mode 100644 index 00000000000..a2aa238e5f7 --- /dev/null +++ b/.semgrep/adapter/bid-type-if-check.yml @@ -0,0 +1,19 @@ +rules: + - id: bid-type-if-check + message: The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to $ORTBTYPE. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the [MType](https://github.com/prebid/openrtb/blob/main/openrtb2/bid.go#L334) field in the response to accurately determine the media type for the impression. + languages: + - go + severity: WARNING + patterns: + - pattern-inside: | + if $CONDITION != nil { + return $ORTBTYPE + } + - metavariable-pattern: + metavariable: $ORTBTYPE + patterns: + - pattern-either: + - pattern: openrtb_ext.$BIDTYPE + - metavariable-regex: + metavariable: $BIDTYPE + regex: BidType* \ No newline at end of file diff --git a/.semgrep/adapter/bid-type-switch-check.go b/.semgrep/adapter/bid-type-switch-check.go new file mode 100644 index 00000000000..c8adf4f9834 --- /dev/null +++ b/.semgrep/adapter/bid-type-switch-check.go @@ -0,0 +1,36 @@ +/* + bid-type-switch-check tests + https://semgrep.dev/docs/writing-rules/testing-rules + "ruleid" prefix in comment indicates patterns that should be flagged by semgrep + "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep +*/ + +// ruleid: bid-type-switch-check +switch bidExt.AdCodeType { +case "banner": + return openrtb_ext.BidTypeBanner, nil +case "native": + return openrtb_ext.BidTypeNative, nil +case "video": + return openrtb_ext.BidTypeVideo, nil +} + +// ruleid: bid-type-switch-check +switch impExt.Adot.MediaType { +case string(openrtb_ext.BidTypeBanner): + return openrtb_ext.BidTypeBanner, nil +case string(openrtb_ext.BidTypeVideo): + return openrtb_ext.BidTypeVideo, nil +case string(openrtb_ext.BidTypeNative): + return openrtb_ext.BidTypeNative, nil +} + +// ok: bid-type-switch-check +switch bid.MType { +case "banner": + return openrtb_ext.BidTypeBanner, nil +case "native": + return openrtb_ext.BidTypeNative, nil +case "video": + return openrtb_ext.BidTypeVideo, nil +} diff --git a/.semgrep/adapter/bid-type-switch-check.yml b/.semgrep/adapter/bid-type-switch-check.yml new file mode 100644 index 00000000000..b39cf870aa7 --- /dev/null +++ b/.semgrep/adapter/bid-type-switch-check.yml @@ -0,0 +1,23 @@ +rules: + - id: bid-type-switch-check + message: The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to $ORTBTYPE. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the [MType](https://github.com/prebid/openrtb/blob/main/openrtb2/bid.go#L334) field in the response to accurately determine the media type for the impression. + languages: + - go + severity: WARNING + patterns: + - pattern-inside: | + switch $BIDTYPE { + case ...: + return $ORTBTYPE, nil + } + - metavariable-regex: + metavariable: $BIDTYPE + regex: ^(?!bid\.MType$).*$ + - metavariable-pattern: + metavariable: $ORTBTYPE + patterns: + - pattern-either: + - pattern: openrtb_ext.$W + - metavariable-regex: + metavariable: $W + regex: BidType* \ No newline at end of file diff --git a/.semgrep/adapter/builder-struct-name.go b/.semgrep/adapter/builder-struct-name.go new file mode 100644 index 00000000000..37a938ca748 --- /dev/null +++ b/.semgrep/adapter/builder-struct-name.go @@ -0,0 +1,124 @@ +/* + builder-struct-name tests + https://semgrep.dev/docs/writing-rules/testing-rules + "ruleid" prefix in comment indicates patterns that should be flagged by semgrep + "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep +*/ + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + foo1 := foo{} + // ruleid: builder-struct-name-check + return &fooadapter{foo: foo1}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &adapterbar{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &fooadapterbar{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &FooAdapter{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &AdapterBar{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &AdapterBar{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + // ruleid: builder-struct-name-check + return &FooAdapterBar{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + foo2 := foo{} + //ruleid: builder-struct-name-check + adpt1 := Adapter{foo: foo2} + return &adpt1, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + //ruleid: builder-struct-name-check + builder := &Adapter{foo{}} + return builder, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + foo3 := foo{} + if foo3.bar == "" { + foo3.bar = "bar" + } + //ruleid: builder-struct-name-check + adpt2 := Adapter{} + adpt2.foo = foo3 + return &adpt2, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + //ruleid: builder-struct-name-check + return &foo{}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + //ruleid: builder-struct-name-check + var obj Adapter + obj.Foo = "foo" + if obj.Bar == "" { + obj.Bar = "bar" + } + return &obj, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + //ruleid: builder-struct-name-check + var obj *FooAdapterBar + obj.Foo = "foo" + if obj.Bar == "" { + obj.Bar = "bar" + } + return obj, nil +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + // ok: builder-struct-name-check + return &adapter{endpoint: "www.foo.com"}, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + builder := &adapter{} + builder.endpoint = "www.foo.com" + // ok: builder-struct-name-check + return builder, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + builder := adapter{} + builder.endpoint = "www.foo.com" + // ok: builder-struct-name-check + return &builder, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + var builder adapter + builder.endpoint = "www.foo.com" + // ok: builder-struct-name-check + return &builder, nil +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + var builder *adapter + builder.endpoint = "www.foo.com" + // ok: builder-struct-name-check + return builder, nil +} \ No newline at end of file diff --git a/.semgrep/adapter/builder-struct-name.yml b/.semgrep/adapter/builder-struct-name.yml new file mode 100644 index 00000000000..bc876ae1809 --- /dev/null +++ b/.semgrep/adapter/builder-struct-name.yml @@ -0,0 +1,58 @@ +rules: + - id: builder-struct-name-check + languages: + - go + message: | + You can call this simply "adapter", the `$BUILDER` identification is already supplied by the package name. As you have it, referencing your adapter from outside the package would be `$BUILDER.$BUILDER` which looks a little redundant. See example below: + + ``` + package foo + + type adapter struct { + endpoint string + } + + func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{endpoint: "https://www.foo.com"}, nil + } + ``` + severity: ERROR + patterns: + - pattern-either: + - pattern-inside: > + func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) { + ... + $BUILDER_OBJ := &$BUILDER{...} + ... + return $BUILDER_OBJ, nil + } + - pattern-inside: > + func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) { + ... + $BUILDER_OBJ := $BUILDER{...} + ... + return &$BUILDER_OBJ, nil + } + - pattern-inside: > + func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) { + ... + return &$BUILDER{...}, ... + } + - pattern-inside: > + func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) { + ... + var $BUILDER_OBJ $BUILDER + ... + return &$BUILDER_OBJ, ... + } + - pattern-inside: > + func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) { + ... + var $BUILDER_OBJ *$BUILDER + ... + return $BUILDER_OBJ, ... + } + - focus-metavariable: $BUILDER + - metavariable-regex: + metavariable: $BUILDER + regex: (?!adapter$) diff --git a/.semgrep/adapter/package-import.go b/.semgrep/adapter/package-import.go new file mode 100644 index 00000000000..a34ce8789cd --- /dev/null +++ b/.semgrep/adapter/package-import.go @@ -0,0 +1,47 @@ +import ( + // ok: package-import-check + "fmt" + // ok: package-import-check + "os" + // ruleid: package-import-check + "github.com/mitchellh/copystructure" + // ruleid: package-import-check + "github.com/golang/glog" +) + +import ( + // ok: package-import-check + "fmt" + // ruleid: package-import-check + cs "github.com/mitchellh/copystructure" + // ok: package-import-check + "os" + // ruleid: package-import-check + log "github.com/golang/glog" +) + +import ( + // ok: package-import-check + "fmt" + // ruleid: package-import-check + cs "github.com/mitchellh/copystructure/subpackage" + // ok: package-import-check + "os" + // ruleid: package-import-check + log "github.com/golang/glog/subpackage" +) + +// ruleid: package-import-check +import "github.com/golang/glog" + +// ruleid: package-import-check +import "github.com/mitchellh/copystructure" + +// ruleid: package-import-check +import log "github.com/golang/glog" + +// ruleid: package-import-check +import copy "github.com/mitchellh/copystructure" + +// ok: package-import-check +import "fmt" diff --git a/.semgrep/adapter/package-import.yml b/.semgrep/adapter/package-import.yml new file mode 100644 index 00000000000..20de2d107da --- /dev/null +++ b/.semgrep/adapter/package-import.yml @@ -0,0 +1,13 @@ +rules: + - id: package-import-check + message: Usage of "$PKG" package is prohibited in adapter code + languages: + - go + severity: ERROR + pattern-either: + - patterns: + - pattern: import "$PKG" + - focus-metavariable: $PKG + - metavariable-regex: + metavariable: $PKG + regex: (^github\.com/mitchellh/copystructure(/.*)?$|^github\.com/golang/glog(/.*)?$) \ No newline at end of file diff --git a/.semgrep/adapter/parse-bid-type-check.go b/.semgrep/adapter/parse-bid-type-check.go new file mode 100644 index 00000000000..77badd14b16 --- /dev/null +++ b/.semgrep/adapter/parse-bid-type-check.go @@ -0,0 +1,29 @@ +/* + parse-bid-type-check tests + https://semgrep.dev/docs/writing-rules/testing-rules + "ruleid" prefix in comment indicates patterns that should be flagged by semgrep + "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep +*/ + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + // ruleid: parse-bid-type-check + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), + } +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + var bidExt bidExt + // ruleid: parse-bid-type-check + bidType, err := openrtb_ext.ParseBidType(bidExt.Prebid.Type) + + return bidType, err +} diff --git a/.semgrep/adapter/parse-bid-type-check.yml b/.semgrep/adapter/parse-bid-type-check.yml new file mode 100644 index 00000000000..daa70684023 --- /dev/null +++ b/.semgrep/adapter/parse-bid-type-check.yml @@ -0,0 +1,10 @@ +rules: + - id: parse-bid-type-check + message: > + Prebid server expects the media type to be explicitly set in the adapter response. + Therefore, recommends implementing a pattern where the adapter server sets the [MType](https://github.com/prebid/openrtb/blob/main/openrtb2/bid.go#L334) field in the response to accurately determine the media type for the impression. + languages: + - go + severity: WARNING + patterns: + - pattern: openrtb_ext.ParseBidType(...) \ No newline at end of file diff --git a/.semgrep/adapter/type-bid-assignment.go b/.semgrep/adapter/type-bid-assignment.go new file mode 100644 index 00000000000..fb29a4912d5 --- /dev/null +++ b/.semgrep/adapter/type-bid-assignment.go @@ -0,0 +1,181 @@ +/* + type-bid-assignment tests + https://semgrep.dev/docs/writing-rules/testing-rules + "ruleid" prefix in comment indicates patterns that should be flagged by semgrep + "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep +*/ + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for _, sb := range seatBid.Bid { + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + // ruleid: type-bid-assignment-check + Bid: &sb, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for _, sb := range seatBid.Bid { + sbcopy := sb + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + // ok: type-bid-assignment-check + Bid: &sbcopy, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for _, sb := range seatBid.Bid { + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + return nil, err + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + // ruleid: type-bid-assignment-check + Bid: &sb, + BidType: bidType, + }) + + } + } +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for _, sb := range seatBid.Bid { + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + return nil, err + } + // ruleid: type-bid-assignment-check + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &sb, BidType: bidType}) + } + } +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + var errors []error + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + var t adapters.TypedBid + // ruleid: type-bid-assignment-check + t.Bid = &bid + bidResponse.Bids = append(bidResponse.Bids, &t) + } + } + return bidResponse, errors +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + var errors []error + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + var t adapters.TypedBid + t = adapters.TypedBid{ + // ruleid: type-bid-assignment-check + Bid: &bid, + } + + bidResponse.Bids = append(bidResponse.Bids, &t) + } + } + return bidResponse, errors +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for idx, _ := range seatBid.Bid { + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + // ok: type-bid-assignment-check + Bid: &seatBid.Bid[idx], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + for _, seatBid := range bidResp.SeatBid { + for idx := range seatBid.Bid { + bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp) + if err != nil { + return nil, err + } + // ok: type-bid-assignment-check + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &seatBid.Bid[idx], BidType: bidType}) + } + } +} diff --git a/.semgrep/adapter/type-bid-assignment.yml b/.semgrep/adapter/type-bid-assignment.yml new file mode 100644 index 00000000000..95fb6811227 --- /dev/null +++ b/.semgrep/adapter/type-bid-assignment.yml @@ -0,0 +1,64 @@ +rules: + - id: type-bid-assignment-check + languages: + - go + message: > + Found incorrect assignment made to $KEY. $BID variable receives a new value in each iteration of range loop. Assigning the address of $BID `(&$BID)` to $KEY will result in a pointer that always points to the same memory address with the value of the last iteration. + This can lead to unexpected behavior or incorrect results. Refer https://go.dev/play/p/9ZS1f-5h4qS + + Consider using an index variable in the seatBids.Bid loop as shown below + + ``` + for _, seatBid := range response.SeatBid { + for i := range seatBids.Bid { + ... + responseBid := &adapters.TypedBid{ + Bid: &seatBids.Bid[i], + ... + } + ... + ... + } + } + ``` + severity: ERROR + patterns: + - pattern-either: + - pattern: > + for _, $BID := range ... { + ... + ... := &adapters.TypedBid{ + $KEY: &$BID, + ... + } + ... + } + - pattern: > + for _, $BID := range ... { + ... + ... = adapters.TypedBid{ + $KEY: &$BID, + ... + } + ... + } + - pattern: > + for _, $BID := range ... { + ... + ... = append(..., &adapters.TypedBid{ + $KEY: &$BID, + ... + }) + ... + } + - pattern: > + for _, $BID := range ... { + var $TYPEBID_OBJ adapters.TypedBid + ... + $TYPEBID_OBJ.$KEY = &$BID + ... + } + - focus-metavariable: $KEY + - metavariable-regex: + metavariable: $KEY + regex: Bid diff --git a/Dockerfile b/Dockerfile index 80269c908df..bff68b614b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget WORKDIR /tmp -RUN wget https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz && \ - tar -xf go1.19.2.linux-amd64.tar.gz && \ +RUN wget https://dl.google.com/go/go1.20.5.linux-amd64.tar.gz && \ + tar -xf go1.20.5.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ @@ -20,7 +20,7 @@ RUN go mod tidy RUN go mod vendor ARG TEST="true" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi -RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . +RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/v2/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/v2/version.Rev=`git rev-parse HEAD`" . FROM ubuntu:20.04 AS release LABEL maintainer="hans.hjort@xandr.com" diff --git a/Makefile b/Makefile index baf69cafbf8..2d8aae6c78a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: deps test build-modules build -.PHONY: deps test build-modules build image +.PHONY: deps test build-modules build image format # deps will clean out the vendor directory and use go mod for a fresh install deps: @@ -15,7 +15,7 @@ test: deps ifeq "$(adapter)" "" ./validate.sh else - go test github.com/prebid/prebid-server/adapters/$(adapter) -bench=. + go test github.com/prebid/prebid-server/v2/adapters/$(adapter) -bench=. endif # build-modules generates modules/builder.go file which provides a list of all available modules @@ -29,3 +29,7 @@ build: test # image will build a docker image image: docker build -t prebid-server . + +# format runs format +format: + ./scripts/format.sh -f true diff --git a/README.md b/README.md index 95e0262b46f..cb64ed9a3b1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid- ## Installation -First install [Go](https://golang.org/doc/install) version 1.18 or newer. +First install [Go](https://golang.org/doc/install) version 1.19 or newer. Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. @@ -47,6 +47,15 @@ go build . ./prebid-server ``` +Run format: +``` +make format +``` +or +```bash +./scripts/format.sh -f true +``` + Load the landing page in your browser at `http://localhost:8000/`. For the full API reference, see [the endpoint documentation](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) diff --git a/account/account.go b/account/account.go index 052d01994ce..2c243e0dd90 100644 --- a/account/account.go +++ b/account/account.go @@ -2,32 +2,27 @@ package account import ( "context" - "encoding/json" "fmt" - "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - jsonpatch "gopkg.in/evanphx/json-patch.v4" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string, me metrics.MetricsEngine) (account *config.Account, errs []error) { - // Check BlacklistedAcctMap until we have deprecated it - if _, found := cfg.BlacklistedAcctMap[accountID]; found { - return nil, []error{&errortypes.BlacklistedAcct{ - Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), - }} - } if cfg.AccountRequired && accountID == metrics.PublisherUnknown { return nil, []error{&errortypes.AcctRequired{ Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), }} } + if accountJSON, accErrs := fetcher.FetchAccount(ctx, cfg.AccountDefaultsJSON(), accountID); len(accErrs) > 0 || accountJSON == nil { // accountID does not reference a valid account for _, e := range accErrs { @@ -49,45 +44,12 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r } else { // accountID resolved to a valid account, merge with AccountDefaults for a complete config account = &config.Account{} - err := json.Unmarshal(accountJSON, account) - - // this logic exists for backwards compatibility. If the initial unmarshal fails above, we attempt to - // resolve it by converting the GDPR enforce purpose fields and then attempting an unmarshal again before - // declaring a malformed account error. - // unmarshal fetched account to determine if it is well-formed - var deprecatedPurposeFields []string - if _, ok := err.(*json.UnmarshalTypeError); ok { - // attempt to convert deprecated GDPR enforce purpose fields to resolve issue - accountJSON, err, deprecatedPurposeFields = ConvertGDPREnforcePurposeFields(accountJSON) - // unmarshal again to check if unmarshal error still exists after GDPR field conversion - err = json.Unmarshal(accountJSON, account) - - if _, ok := err.(*json.UnmarshalTypeError); ok { - return nil, []error{&errortypes.MalformedAcct{ - Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), - }} - } + if err := jsonutil.UnmarshalValid(accountJSON, account); err != nil { + return nil, []error{&errortypes.MalformedAcct{ + Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), + }} } - usingGDPRChannelEnabled := useGDPRChannelEnabled(account) - usingCCPAChannelEnabled := useCCPAChannelEnabled(account) - if usingGDPRChannelEnabled { - me.RecordAccountGDPRChannelEnabledWarning(accountID) - } - if usingCCPAChannelEnabled { - me.RecordAccountCCPAChannelEnabledWarning(accountID) - } - for _, purposeName := range deprecatedPurposeFields { - me.RecordAccountGDPRPurposeWarning(accountID, purposeName) - } - if len(deprecatedPurposeFields) > 0 || usingGDPRChannelEnabled || usingCCPAChannelEnabled { - me.RecordAccountUpgradeStatus(accountID) - } - - if err != nil { - errs = append(errs, err) - return nil, errs - } // Fill in ID if needed, so it can be left out of account definition if len(account.ID) == 0 { account.ID = accountID @@ -97,14 +59,19 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r setDerivedConfig(account) } if account.Disabled { - errs = append(errs, &errortypes.BlacklistedAcct{ + errs = append(errs, &errortypes.AccountDisabled{ Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID), }) return nil, errs } - // set the value of events.enabled field based on deprecated events_enabled field and ensure backward compatibility - deprecateEventsEnabledField(account) + if ipV6Err := account.Privacy.IPv6Config.Validate(nil); len(ipV6Err) > 0 { + account.Privacy.IPv6Config.AnonKeepBits = iputil.IPv6DefaultMaskingBitSize + } + + if ipV4Err := account.Privacy.IPv4Config.Validate(nil); len(ipV4Err) > 0 { + account.Privacy.IPv4Config.AnonKeepBits = iputil.IPv4DefaultMaskingBitSize + } return account, nil } @@ -170,102 +137,3 @@ func setDerivedConfig(account *config.Account) { } } } - -// PatchAccount represents the GDPR portion of a publisher account configuration that can be mutated -// for backwards compatibility reasons -type PatchAccount struct { - GDPR map[string]*PatchAccountGDPRPurpose `json:"gdpr"` -} - -// PatchAccountGDPRPurpose represents account-specific GDPR purpose configuration data that can be mutated -// for backwards compatibility reasons -type PatchAccountGDPRPurpose struct { - EnforceAlgo string `json:"enforce_algo,omitempty"` - EnforcePurpose *bool `json:"enforce_purpose,omitempty"` -} - -// ConvertGDPREnforcePurposeFields is responsible for ensuring account GDPR config backwards compatibility -// given the recent type change of gdpr.purpose{1-10}.enforce_purpose from a string to a bool. This function -// iterates over each GDPR purpose config and sets enforce_purpose and enforce_algo to the appropriate -// bool and string values respectively if enforce_purpose is a string and enforce_algo is not set -func ConvertGDPREnforcePurposeFields(config []byte) (newConfig []byte, err error, deprecatedPurposeFields []string) { - gdprJSON, _, _, err := jsonparser.Get(config, "gdpr") - if err != nil && err == jsonparser.KeyPathNotFoundError { - return config, nil, deprecatedPurposeFields - } - if err != nil { - return nil, err, deprecatedPurposeFields - } - - newAccount := PatchAccount{ - GDPR: map[string]*PatchAccountGDPRPurpose{}, - } - - for i := 1; i <= 10; i++ { - purposeName := fmt.Sprintf("purpose%d", i) - - enforcePurpose, purposeDataType, _, err := jsonparser.Get(gdprJSON, purposeName, "enforce_purpose") - if err != nil && err == jsonparser.KeyPathNotFoundError { - continue - } - if err != nil { - return nil, err, deprecatedPurposeFields - } - if purposeDataType != jsonparser.String { - continue - } else { - deprecatedPurposeFields = append(deprecatedPurposeFields, purposeName) - } - - _, _, _, err = jsonparser.Get(gdprJSON, purposeName, "enforce_algo") - if err != nil && err != jsonparser.KeyPathNotFoundError { - return nil, err, deprecatedPurposeFields - } - if err == nil { - continue - } - - newEnforcePurpose := false - if string(enforcePurpose) == "full" { - newEnforcePurpose = true - } - - newAccount.GDPR[purposeName] = &PatchAccountGDPRPurpose{ - EnforceAlgo: "full", - EnforcePurpose: &newEnforcePurpose, - } - } - - patchConfig, err := json.Marshal(newAccount) - if err != nil { - return nil, err, deprecatedPurposeFields - } - - newConfig, err = jsonpatch.MergePatch(config, patchConfig) - if err != nil { - return nil, err, deprecatedPurposeFields - } - - return newConfig, nil, deprecatedPurposeFields -} - -func useGDPRChannelEnabled(account *config.Account) bool { - return account.GDPR.ChannelEnabled.IsSet() && !account.GDPR.IntegrationEnabled.IsSet() -} - -func useCCPAChannelEnabled(account *config.Account) bool { - return account.CCPA.ChannelEnabled.IsSet() && !account.CCPA.IntegrationEnabled.IsSet() -} - -// deprecateEventsEnabledField is responsible for ensuring backwards compatibility of "events_enabled" field. -// This function favors "events.enabled" field over deprecated "events_enabled" field, if values for both are set. -// If only deprecated "events_enabled" field is set then it sets the same value to "events.enabled" field. -func deprecateEventsEnabledField(account *config.Account) { - if account != nil { - if account.Events.Enabled == nil { - account.Events.Enabled = account.EventsEnabled - } - // assign the old value to the new value so old and new are always the same even though the new value is what is used in the application code. - account.EventsEnabled = account.Events.Enabled - } -} diff --git a/account/account_test.go b/account/account_test.go index 4bac0894577..369c2d2c40d 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,34 +6,23 @@ import ( "fmt" "testing" - "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/iputil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) var mockAccountData = map[string]json.RawMessage{ "valid_acct": json.RawMessage(`{"disabled":false}`), + "invalid_acct_ipv6_ipv4": json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`), "disabled_acct": json.RawMessage(`{"disabled":true}`), "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), "gdpr_channel_enabled_acct": json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`), "ccpa_channel_enabled_acct": json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`), - "gdpr_channel_enabled_deprecated_purpose_acct": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}, "channel_enabled":{"amp":true}}}`), - "gdpr_deprecated_purpose1": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose2": json.RawMessage(`{"disabled":false,"gdpr":{"purpose2":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose3": json.RawMessage(`{"disabled":false,"gdpr":{"purpose3":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose4": json.RawMessage(`{"disabled":false,"gdpr":{"purpose4":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose5": json.RawMessage(`{"disabled":false,"gdpr":{"purpose5":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose6": json.RawMessage(`{"disabled":false,"gdpr":{"purpose6":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose7": json.RawMessage(`{"disabled":false,"gdpr":{"purpose7":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose8": json.RawMessage(`{"disabled":false,"gdpr":{"purpose8":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose9": json.RawMessage(`{"disabled":false,"gdpr":{"purpose9":{"enforce_purpose":"full"}}}`), - "gdpr_deprecated_purpose10": json.RawMessage(`{"disabled":false,"gdpr":{"purpose10":{"enforce_purpose":"full"}}}`), } type mockAccountFetcher struct { @@ -54,22 +43,21 @@ func TestGetAccount(t *testing.T) { required bool // account_defaults.disabled disabled bool + // checkDefaultIP indicates IPv6 and IPv6 should be set to default values + checkDefaultIP bool // expected error, or nil if account should be found err error }{ - // Blacklisted account is always rejected even in permissive setup - {accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - // empty pubID {accountID: unknown, required: false, disabled: false, err: nil}, {accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}}, - {accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: unknown, required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given but is not a valid host account (does not exist) {accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil}, {accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil}, - {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}}, // pubID given and matches a valid host account with Disabled: false @@ -78,17 +66,13 @@ func TestGetAccount(t *testing.T) { {accountID: "valid_acct", required: false, disabled: true, err: nil}, {accountID: "valid_acct", required: true, disabled: true, err: nil}, - // pubID given and matches a host account explicitly disabled (Disabled: true on account json) - {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, - {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, checkDefaultIP: true}, - // pubID given and matches a host account with Disabled: false and GDPR purpose data to convert - {accountID: "gdpr_deprecated_purpose1", required: false, disabled: false, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: true, disabled: false, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: false, disabled: true, err: nil}, - {accountID: "gdpr_deprecated_purpose1", required: true, disabled: true, err: nil}, + // pubID given and matches a host account explicitly disabled (Disabled: true on account json) + {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, + {accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.AccountDisabled{}}, // pubID given and matches a host account that has a malformed config {accountID: "malformed_acct", required: false, disabled: false, err: &errortypes.MalformedAcct{}}, @@ -99,7 +83,7 @@ func TestGetAccount(t *testing.T) { // account not provided (does not exist) {accountID: "", required: false, disabled: false, err: nil}, {accountID: "", required: true, disabled: false, err: nil}, - {accountID: "", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}}, + {accountID: "", required: false, disabled: true, err: &errortypes.AccountDisabled{}}, {accountID: "", required: true, disabled: true, err: &errortypes.AcctRequired{}}, } @@ -107,15 +91,13 @@ func TestGetAccount(t *testing.T) { description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled) t.Run(description, func(t *testing.T) { cfg := &config.Configuration{ - BlacklistedAcctMap: map[string]bool{"bad_acct": true}, - AccountRequired: test.required, - AccountDefaults: config.Account{Disabled: test.disabled}, + AccountRequired: test.required, + AccountDefaults: config.Account{Disabled: test.disabled}, } fetcher := &mockAccountFetcher{} assert.NoError(t, cfg.MarshalAccountDefaults()) metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() account, errors := GetAccount(context.Background(), cfg, fetcher, test.accountID, metrics) @@ -129,6 +111,10 @@ func TestGetAccount(t *testing.T) { assert.Nil(t, account, "return account must be nil on error") assert.IsType(t, test.err, errors[0], "error is of unexpected type") } + if test.checkDefaultIP { + assert.Equal(t, account.Privacy.IPv6Config.AnonKeepBits, iputil.IPv6DefaultMaskingBitSize, "ipv6 should be set to default value") + assert.Equal(t, account.Privacy.IPv4Config.AnonKeepBits, iputil.IPv4DefaultMaskingBitSize, "ipv4 should be set to default value") + } }) } } @@ -228,418 +214,3 @@ func TestSetDerivedConfig(t *testing.T) { assert.Equal(t, account.GDPR.Purpose1.EnforceAlgoID, tt.wantEnforceAlgoID, tt.description) } } - -func TestConvertGDPREnforcePurposeFields(t *testing.T) { - enforcePurposeNo := `{"enforce_purpose":"no"}` - enforcePurposeNoMapped := `{"enforce_algo":"full", "enforce_purpose":false}` - enforcePurposeFull := `{"enforce_purpose":"full"}` - enforcePurposeFullMapped := `{"enforce_algo":"full", "enforce_purpose":true}` - - tests := []struct { - description string - giveConfig []byte - wantConfig []byte - wantErr error - wantPurposeFields []string - }{ - { - description: "config is nil", - giveConfig: nil, - wantConfig: nil, - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "config is empty - no gdpr key", - giveConfig: []byte(``), - wantConfig: []byte(``), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr present but empty", - giveConfig: []byte(`{"gdpr": {}}`), - wantConfig: []byte(`{"gdpr": {}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr present but invalid", - giveConfig: []byte(`{"gdpr": {`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1 present but empty", - giveConfig: []byte(`{"gdpr":{"purpose1":{}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to bool", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string full", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":true}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string no", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string no and other fields are untouched during conversion", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no", "enforce_vendors":true}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false, "enforce_vendors":true}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set but invalid", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_purpose":}}}`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_algo is set", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`), - wantErr: nil, - wantPurposeFields: nil, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), - wantConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set but invalid", - giveConfig: []byte(`{"gdpr":{"purpose1":{"enforce_algo":, "enforce_purpose":"full"}}}`), - wantConfig: nil, - wantErr: jsonparser.MalformedJsonError, - wantPurposeFields: []string{"purpose1"}, - }, - { - description: "gdpr.purpose{1-10}.enforce_purpose are set to strings no and full alternating", - giveConfig: []byte(`{"gdpr":{` + - `"purpose1":` + enforcePurposeNo + - `,"purpose2":` + enforcePurposeFull + - `,"purpose3":` + enforcePurposeNo + - `,"purpose4":` + enforcePurposeFull + - `,"purpose5":` + enforcePurposeNo + - `,"purpose6":` + enforcePurposeFull + - `,"purpose7":` + enforcePurposeNo + - `,"purpose8":` + enforcePurposeFull + - `,"purpose9":` + enforcePurposeNo + - `,"purpose10":` + enforcePurposeFull + - `}}`), - wantConfig: []byte(`{"gdpr":{` + - `"purpose1":` + enforcePurposeNoMapped + - `,"purpose2":` + enforcePurposeFullMapped + - `,"purpose3":` + enforcePurposeNoMapped + - `,"purpose4":` + enforcePurposeFullMapped + - `,"purpose5":` + enforcePurposeNoMapped + - `,"purpose6":` + enforcePurposeFullMapped + - `,"purpose7":` + enforcePurposeNoMapped + - `,"purpose8":` + enforcePurposeFullMapped + - `,"purpose9":` + enforcePurposeNoMapped + - `,"purpose10":` + enforcePurposeFullMapped + - `}}`), - wantErr: nil, - wantPurposeFields: []string{"purpose1", "purpose2", "purpose3", "purpose4", "purpose5", "purpose6", "purpose7", "purpose8", "purpose9", "purpose10"}, - }, - } - - for _, tt := range tests { - metricsMock := &metrics.MetricsEngineMock{} - metricsMock.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() - metricsMock.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - newConfig, err, deprecatedPurposeFields := ConvertGDPREnforcePurposeFields(tt.giveConfig) - if tt.wantErr != nil { - assert.Error(t, err, tt.description) - } - - if len(tt.wantConfig) == 0 { - assert.Equal(t, tt.wantConfig, newConfig, tt.description) - } else { - assert.JSONEq(t, string(tt.wantConfig), string(newConfig), tt.description) - } - assert.Equal(t, tt.wantPurposeFields, deprecatedPurposeFields, tt.description) - } -} - -func TestGdprCcpaChannelEnabledMetrics(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenAccountID string - givenMetric string - expectedMetricCount int - }{ - { - name: "ChannelEnabledGDPR", - givenAccountID: "gdpr_channel_enabled_acct", - givenMetric: "RecordAccountGDPRChannelEnabledWarning", - expectedMetricCount: 1, - }, - { - name: "ChannelEnabledCCPA", - givenAccountID: "ccpa_channel_enabled_acct", - givenMetric: "RecordAccountCCPAChannelEnabledWarning", - expectedMetricCount: 1, - }, - { - name: "NotChannelEnabledCCPA", - givenAccountID: "valid_acct", - givenMetric: "RecordAccountCCPAChannelEnabledWarning", - expectedMetricCount: 0, - }, - { - name: "NotChannelEnabledGDPR", - givenAccountID: "valid_acct", - givenMetric: "RecordAccountGDPRChannelEnabledWarning", - expectedMetricCount: 0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On(test.givenMetric, mock.Anything, mock.Anything).Return() - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - _, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics) - - metrics.AssertNumberOfCalls(t, test.givenMetric, test.expectedMetricCount) - }) - } -} - -func TestGdprPurposeWarningMetrics(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenMetric string - givenAccountID string - givenConfig []byte - expectedMetricCount int - }{ - { - name: "Purpose1MetricCalled", - givenAccountID: "gdpr_deprecated_purpose1", - expectedMetricCount: 1, - }, - { - name: "Purpose2MetricCalled", - givenAccountID: "gdpr_deprecated_purpose2", - expectedMetricCount: 1, - }, - { - name: "Purpose3MetricCalled", - givenAccountID: "gdpr_deprecated_purpose3", - expectedMetricCount: 1, - }, - { - name: "Purpose4MetricCalled", - givenAccountID: "gdpr_deprecated_purpose4", - expectedMetricCount: 1, - }, - { - name: "Purpose5MetricCalled", - givenAccountID: "gdpr_deprecated_purpose5", - expectedMetricCount: 1, - }, - { - name: "Purpose6MetricCalled", - givenAccountID: "gdpr_deprecated_purpose6", - expectedMetricCount: 1, - }, - { - name: "Purpose7MetricCalled", - givenAccountID: "gdpr_deprecated_purpose7", - expectedMetricCount: 1, - }, - { - name: "Purpose8MetricCalled", - givenAccountID: "gdpr_deprecated_purpose8", - expectedMetricCount: 1, - }, - { - name: "Purpose9MetricCalled", - givenAccountID: "gdpr_deprecated_purpose9", - expectedMetricCount: 1, - }, - { - name: "Purpose10MetricCalled", - givenAccountID: "gdpr_deprecated_purpose10", - expectedMetricCount: 1, - }, - { - name: "PurposeMetricNotCalled", - givenAccountID: "valid_acct", - expectedMetricCount: 0, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return() - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - _, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics) - - metrics.AssertNumberOfCalls(t, "RecordAccountGDPRPurposeWarning", test.expectedMetricCount) - }) - - } -} - -func TestAccountUpgradeStatusGetAccount(t *testing.T) { - cfg := &config.Configuration{} - fetcher := &mockAccountFetcher{} - assert.NoError(t, cfg.MarshalAccountDefaults()) - - testCases := []struct { - name string - givenAccountIDs []string - givenMetrics []string - expectedMetricCount int - }{ - { - name: "MultipleDeprecatedConfigs", - givenAccountIDs: []string{"gdpr_channel_enabled_deprecated_purpose_acct"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"}, - expectedMetricCount: 1, - }, - { - name: "ZeroDeprecatedConfigs", - givenAccountIDs: []string{"valid_acct"}, - givenMetrics: []string{}, - expectedMetricCount: 0, - }, - { - name: "OneDeprecatedConfigPurpose", - givenAccountIDs: []string{"gdpr_deprecated_purpose1"}, - givenMetrics: []string{"RecordAccountGDPRPurposeWarning"}, - expectedMetricCount: 1, - }, - { - name: "OneDeprecatedConfigGDPRChannelEnabled", - givenAccountIDs: []string{"gdpr_channel_enabled_acct"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning"}, - expectedMetricCount: 1, - }, - { - name: "OneDeprecatedConfigCCPAChannelEnabled", - givenAccountIDs: []string{"ccpa_channel_enabled_acct"}, - givenMetrics: []string{"RecordAccountCCPAChannelEnabledWarning"}, - expectedMetricCount: 1, - }, - { - name: "MultipleAccountsWithDeprecatedConfigs", - givenAccountIDs: []string{"gdpr_channel_enabled_acct", "gdpr_deprecated_purpose1"}, - givenMetrics: []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"}, - expectedMetricCount: 2, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - metrics := &metrics.MetricsEngineMock{} - for _, metric := range test.givenMetrics { - metrics.Mock.On(metric, mock.Anything, mock.Anything).Return() - } - metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return() - - for _, accountID := range test.givenAccountIDs { - _, _ = GetAccount(context.Background(), cfg, fetcher, accountID, metrics) - } - metrics.AssertNumberOfCalls(t, "RecordAccountUpgradeStatus", test.expectedMetricCount) - }) - } -} - -func TestDeprecateEventsEnabledField(t *testing.T) { - testCases := []struct { - name string - account *config.Account - want *bool - }{ - { - name: "account is nil", - account: nil, - want: nil, - }, - { - name: "account.EventsEnabled is nil, account.Events.Enabled is nil", - account: &config.Account{ - EventsEnabled: nil, - Events: config.Events{ - Enabled: nil, - }, - }, - want: nil, - }, - { - name: "account.EventsEnabled is nil, account.Events.Enabled is non-nil", - account: &config.Account{ - EventsEnabled: nil, - Events: config.Events{ - Enabled: ptrutil.ToPtr(true), - }, - }, - want: ptrutil.ToPtr(true), - }, - { - name: "account.EventsEnabled is non-nil, account.Events.Enabled is nil", - account: &config.Account{ - EventsEnabled: ptrutil.ToPtr(true), - Events: config.Events{ - Enabled: nil, - }, - }, - want: ptrutil.ToPtr(true), - }, - { - name: "account.EventsEnabled is non-nil, account.Events.Enabled is non-nil", - account: &config.Account{ - EventsEnabled: ptrutil.ToPtr(false), - Events: config.Events{ - Enabled: ptrutil.ToPtr(true), - }, - }, - want: ptrutil.ToPtr(true), - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - deprecateEventsEnabledField(test.account) - if test.account != nil { - assert.Equal(t, test.want, test.account.Events.Enabled) - } - }) - } -} diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index 26349e8426b..c7c3300a648 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TtxAdapter struct { diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index bdc546a9627..c84ca0ad1d2 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -3,9 +3,9 @@ package ttx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/33across/params_test.go b/adapters/33across/params_test.go index 2d488c4148c..ba985b2b250 100644 --- a/adapters/33across/params_test.go +++ b/adapters/33across/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/33across.json diff --git a/adapters/aax/aax.go b/adapters/aax/aax.go index 86994c6dea2..a36bf3ad37e 100644 --- a/adapters/aax/aax.go +++ b/adapters/aax/aax.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/aax/aax_test.go b/adapters/aax/aax_test.go index 6a5eaed5dfe..c4fd1c392aa 100644 --- a/adapters/aax/aax_test.go +++ b/adapters/aax/aax_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/aax/params_test.go b/adapters/aax/params_test.go index edf9fb6fc48..bdfa46a9e63 100644 --- a/adapters/aax/params_test.go +++ b/adapters/aax/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/aax.json diff --git a/adapters/aceex/aceex.go b/adapters/aceex/aceex.go index 61863f0b8a8..a87a2b11fcf 100644 --- a/adapters/aceex/aceex.go +++ b/adapters/aceex/aceex.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/aceex/aceex_test.go b/adapters/aceex/aceex_test.go index ec0e0fec710..71c26ed0bed 100644 --- a/adapters/aceex/aceex_test.go +++ b/adapters/aceex/aceex_test.go @@ -3,9 +3,9 @@ package aceex import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/aceex/params_test.go b/adapters/aceex/params_test.go index 220adb23379..cb6445c491a 100644 --- a/adapters/aceex/params_test.go +++ b/adapters/aceex/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index 4370beb72d1..9152e80b83f 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AcuityAdsAdapter struct { diff --git a/adapters/acuityads/acuityads_test.go b/adapters/acuityads/acuityads_test.go index ea9d4f24352..c426d02c533 100644 --- a/adapters/acuityads/acuityads_test.go +++ b/adapters/acuityads/acuityads_test.go @@ -3,9 +3,9 @@ package acuityads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/acuityads/params_test.go b/adapters/acuityads/params_test.go index 892fe9a646d..3c7b3a97914 100644 --- a/adapters/acuityads/params_test.go +++ b/adapters/acuityads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 39b1e945f7d..d14ff015ced 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -12,9 +12,9 @@ import ( "github.com/mitchellh/copystructure" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index 7ff817559cc..0f14a05e947 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go index bf8af8d6845..20e4f3dde32 100644 --- a/adapters/adf/adf_test.go +++ b/adapters/adf/adf_test.go @@ -3,9 +3,9 @@ package adf import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index 0b05519df3b..dc0d84927bc 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adf.json diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index a2a10ed51f2..88166ec237d 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -11,10 +11,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdgenerationAdapter struct { diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index c204fbd320d..3e94ac6c382 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adgeneration/params_test.go b/adapters/adgeneration/params_test.go index 062d122ac08..58400e96656 100644 --- a/adapters/adgeneration/params_test.go +++ b/adapters/adgeneration/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 429dab2301d..29c21ef3b99 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -10,13 +10,12 @@ import ( "strings" "text/template" - "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdheseAdapter struct { @@ -142,6 +141,9 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed as generic Adhese bid.", string(response.Body)))} } + if len(adheseBidResponseArray) == 0 { + return nil, nil + } var adheseBid = adheseBidResponseArray[0] if adheseBid.Origin == "JERLICIA" { @@ -201,7 +203,6 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb2.BidResponse { adheseExtJson, err := json.Marshal(adheseOriginData) if err != nil { - glog.Error(fmt.Sprintf("Unable to parse adhese Origin Data as JSON due to %v", err)) adheseExtJson = make([]byte, 0) } return openrtb2.BidResponse{ diff --git a/adapters/adhese/adhese_test.go b/adapters/adhese/adhese_test.go index d09a29ee9bd..2b70bb001a6 100644 --- a/adapters/adhese/adhese_test.go +++ b/adapters/adhese/adhese_test.go @@ -3,9 +3,9 @@ package adhese import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json b/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json new file mode 100644 index 00000000000..2a778bdf0b4 --- /dev/null +++ b/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json @@ -0,0 +1,54 @@ +{ + "mockBidRequest": { + "id": "test-req", + "user": { + "ext": { + "consent" : "dummy" + } + }, + "imp": [ + { + "id": "test-req", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "account": "demo", + "location": "_adhese_prebid_demo_", + "format": "leaderboard", + "targets": + { + "ci": ["gent", "brussels"], + "ag": ["55"], + "tl": ["all"] + } + } + } + } + ], + "site": { + "id": "test", + "publisher": { + "id": "123" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + }, + "mockResponse": { + "status": 200, + "body": [] + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/adhese/adhesetest/supplemental/res-no_bids.json b/adapters/adhese/adhesetest/supplemental/res-no_bids_204.json similarity index 100% rename from adapters/adhese/adhesetest/supplemental/res-no_bids.json rename to adapters/adhese/adhesetest/supplemental/res-no_bids_204.json diff --git a/adapters/adhese/params_test.go b/adapters/adhese/params_test.go index 45024749b2d..1a0aa381cb1 100644 --- a/adapters/adhese/params_test.go +++ b/adapters/adhese/params_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 478b2ad3e2f..e8ccc360e0e 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adkernelAdapter struct { @@ -220,6 +220,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), } } + if len(bidResp.SeatBid) != 1 { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))), @@ -228,7 +229,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e seatBid := bidResp.SeatBid[0] bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) - + bidResponse.Currency = bidResp.Cur for i := 0; i < len(seatBid.Bid); i++ { bid := seatBid.Bid[i] bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ diff --git a/adapters/adkernel/adkernel_test.go b/adapters/adkernel/adkernel_test.go index ae35f712400..2639eb25624 100644 --- a/adapters/adkernel/adkernel_test.go +++ b/adapters/adkernel/adkernel_test.go @@ -3,9 +3,9 @@ package adkernel import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json b/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json index 990903e15f6..24f86378fe5 100644 --- a/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json +++ b/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json @@ -23,7 +23,8 @@ }, "user": { "buyeruid": "A-38327932832" - } + }, + "cur": ["TYR"] }, "httpCalls": [ @@ -45,7 +46,8 @@ }, "user": { "buyeruid": "A-38327932832" - } + }, + "cur": ["TYR"] } }, "mockResponse": { @@ -67,7 +69,8 @@ "w": 300 }] }], - "bidid": "wehM-93KGr0" + "bidid": "wehM-93KGr0", + "cur": "TYR" } } } @@ -75,7 +78,7 @@ "expectedBidResponses": [ { - "currency": "USD", + "currency": "TYR", "bids": [ { "bid": { diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 45e9e41c10c..218708cb697 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adkernelAdnAdapter struct { diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go index 651d82be3b6..e43d00bf0bf 100644 --- a/adapters/adkernelAdn/adkernelAdn_test.go +++ b/adapters/adkernelAdn/adkernelAdn_test.go @@ -3,9 +3,9 @@ package adkernelAdn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 5350fa7cb86..48a4dff961c 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AdmanAdapter struct diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index 608232cc4b8..5617035c713 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -3,9 +3,9 @@ package adman import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adman/params_test.go b/adapters/adman/params_test.go index a80c2a44b8b..9d7e0c16d51 100644 --- a/adapters/adman/params_test.go +++ b/adapters/adman/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the adman schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index 9a07a8922a9..5c68518ee52 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdmixerAdapter struct { diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go index 766f890cdf7..5985d4303c9 100644 --- a/adapters/admixer/admixer_test.go +++ b/adapters/admixer/admixer_test.go @@ -3,9 +3,9 @@ package admixer import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index bfa75a4884f..af85569b460 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/admixer.json diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go index 21830deb9c3..cb8d876fb53 100644 --- a/adapters/adnuntius/adnuntius.go +++ b/adapters/adnuntius/adnuntius.go @@ -10,11 +10,11 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" ) type QueryString map[string]string @@ -27,31 +27,41 @@ type adnAdunit struct { AuId string `json:"auId"` TargetId string `json:"targetId"` Dimensions [][]int64 `json:"dimensions,omitempty"` + MaxDeals int `json:"maxDeals,omitempty"` } type extDeviceAdnuntius struct { NoCookies bool `json:"noCookies,omitempty"` } +type Ad struct { + Bid struct { + Amount float64 + Currency string + } + NetBid struct { + Amount float64 + } + GrossBid struct { + Amount float64 + } + DealID string `json:"dealId,omitempty"` + AdId string + CreativeWidth string + CreativeHeight string + CreativeId string + LineItemId string + Html string + DestinationUrls map[string]string +} + type AdUnit struct { AuId string TargetId string Html string ResponseId string - Ads []struct { - Bid struct { - Amount float64 - Currency string - } - DealID string `json:"dealId,omitempty"` - AdId string - CreativeWidth string - CreativeHeight string - CreativeId string - LineItemId string - Html string - DestinationUrls map[string]string - } + Ads []Ad + Deals []Ad `json:"deals,omitempty"` } type AdnResponse struct { @@ -190,6 +200,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters Message: fmt.Sprintf("ignoring imp id=%s, Adnuntius supports only Banner", imp.ID), }} } + var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, []error{&errortypes.BadInput{ @@ -200,7 +211,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters var adnuntiusExt openrtb_ext.ImpExtAdnunitus if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Error unmarshalling ExtImpBmtm: %s", err.Error()), + Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), }} } @@ -213,13 +224,17 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters network = adnuntiusExt.Network } + adUnit := adnAdunit{ + AuId: adnuntiusExt.Auid, + TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), + Dimensions: getImpSizes(imp), + } + if adnuntiusExt.MaxDeals > 0 { + adUnit.MaxDeals = adnuntiusExt.MaxDeals + } networkAdunitMap[network] = append( networkAdunitMap[network], - adnAdunit{ - AuId: adnuntiusExt.Auid, - TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), - Dimensions: getImpSizes(imp), - }) + adUnit) } endpoint, err := makeEndpointUrl(ortbRequest, a, noCookies) @@ -320,6 +335,71 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) { return gdpr, consent, nil } +func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) { + + creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) + if widthErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth), + }} + } + + creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64) + if heightErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight), + }} + } + + price := ad.Bid.Amount + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), + }} + } + + var adnuntiusExt openrtb_ext.ImpExtAdnunitus + if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), + }} + } + + if adnuntiusExt.BidType != "" { + if strings.EqualFold(string(adnuntiusExt.BidType), "net") { + price = ad.NetBid.Amount + } + if strings.EqualFold(string(adnuntiusExt.BidType), "gross") { + price = ad.GrossBid.Amount + } + } + + adDomain := []string{} + for _, url := range ad.DestinationUrls { + domainArray := strings.Split(url, "/") + domain := strings.Replace(domainArray[2], "www.", "", -1) + adDomain = append(adDomain, domain) + } + + bid := openrtb2.Bid{ + ID: ad.AdId, + ImpID: imp.ID, + W: creativeWidth, + H: creativeHeight, + AdID: ad.AdId, + DealID: ad.DealID, + CID: ad.LineItemId, + CrID: ad.CreativeId, + Price: price * 1000, + AdM: html, + ADomain: adDomain, + } + return &bid, nil + +} + func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adnResponse.AdUnits)) var currency string @@ -329,7 +409,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) adunitMap[adnRespAdunit.TargetId] = adnRespAdunit } - for i, imp := range request.Imp { + for _, imp := range request.Imp { auId, _, _, err := jsonparser.Get(imp.Ext, "bidder", "auId") if err != nil { @@ -344,48 +424,34 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) if len(adunit.Ads) > 0 { ad := adunit.Ads[0] - currency = ad.Bid.Currency - creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) - if widthErr != nil { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth), - }} - } - - creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64) - if heightErr != nil { + adBid, err := generateAdResponse(ad, imp, adunit.Html, request) + if err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight), + Message: fmt.Sprintf("Error at ad generation"), }} } - adDomain := []string{} - for _, url := range ad.DestinationUrls { - domainArray := strings.Split(url, "/") - domain := strings.Replace(domainArray[2], "www.", "", -1) - adDomain = append(adDomain, domain) - } - - bid := openrtb2.Bid{ - ID: ad.AdId, - ImpID: request.Imp[i].ID, - W: creativeWidth, - H: creativeHeight, - AdID: ad.AdId, - DealID: ad.DealID, - CID: ad.LineItemId, - CrID: ad.CreativeId, - Price: ad.Bid.Amount * 1000, - AdM: adunit.Html, - ADomain: adDomain, - } - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, + Bid: adBid, BidType: "banner", }) + + for _, deal := range adunit.Deals { + dealBid, err := generateAdResponse(deal, imp, deal.Html, request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error at ad generation"), + }} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: dealBid, + BidType: "banner", + }) + } + } } diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go index 9c431c2a315..f6edb313708 100644 --- a/adapters/adnuntius/adnuntius_test.go +++ b/adapters/adnuntius/adnuntius_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json new file mode 100644 index 00000000000..d6301fe71cf --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": "gross" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { "amount": 20.0, "currency": "NOK" }, + "grossBid": {"amount": 0.1, "currency": "NOK"}, + "netBid": {"amount": 0.075, "currency": "NOK"}, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 100, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json new file mode 100644 index 00000000000..ebb25b2b7ad --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": "net" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { "amount": 20.0, "currency": "NOK" }, + "grossBid": {"amount": 0.1, "currency": "NOK"}, + "netBid": {"amount": 0.075, "currency": "NOK"}, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 75, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json new file mode 100644 index 00000000000..89016087a43 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "bidType": 123 + } + } + } + ] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Error unmarshalling ExtImpValues: json: cannot unmarshal number into Go struct field ImpExtAdnunitus.bidType of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json index 1f213a43b6a..1987fb9d08e 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json @@ -81,7 +81,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeHeight of type string", + "value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeHeight of type string", "comparison": "literal" } ] diff --git a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json new file mode 100644 index 00000000000..1d4c5bf0747 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "maxDeals": 2 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]], + "maxDeals": 2 + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "matchedAdCount": 1, + "responseId": "adn-rsp-815135818", + "deals": [ + { + "destinationUrlEsc": "", + "clickUrl": "https://click.url", + "urls": { + "url": "https://delivery.adnuntius.com/c/eKOchNsBJzE.net" + }, + "destinationUrls": { + "url": "http://www.google.com" + }, + "cpm": { + "amount": 1.0, + "currency": "NOK" + }, + "bid": { + "amount": 0.001, + "currency": "NOK" + }, + "grossBid": { + "amount": 0.001, + "currency": "NOK" + }, + "netBid": { + "amount": 0.001, + "currency": "NOK" + }, + "cost": { + "amount": 0.001, + "currency": "NOK" + }, + "dealId": "deal_123", + "adId": "adn-id-116330612", + "creativeWidth": "300", + "creativeHeight": "250", + "creativeId": "dl0knc1lnks9jgvx", + "lineItemId": "6l5w2d29kz3vkprq", + "layoutId": "adnuntius_image_layout_1", + "layoutName": "Image", + "layoutExternalReference": "", + "html": "" + } + ], + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 0.001, + "currency": "NOK" + }, + "adId": "adn-id-1559784095", + "creativeWidth": "300", + "creativeHeight": "250", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ], + "metaData": { + "usi": "5dlpmw0d00btldjdvk1lp8rl", + "sessionId": "e4ada7251c93291a871f8e4319cc8fe5" + }, + "duplicateFilter": "AAAAAwAAAAAAJ33PAAAAAAAhrK4AAAAAACYNPAAAAAA=", + "segments": [], + "keywords": [] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784095", + "impid": "test-imp-id", + "price": 1, + "adm": "", + "adid": "adn-id-1559784095", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "adn-id-116330612", + "impid": "test-imp-id", + "price": 1, + "adm": "", + "adid": "adn-id-116330612", + "adomain": ["google.com"], + "cid": "6l5w2d29kz3vkprq", + "crid": "dl0knc1lnks9jgvx", + "dealid": "deal_123", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json index 1fd8a83f9dd..4f109942b91 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json @@ -81,7 +81,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeWidth of type string", + "value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeWidth of type string", "comparison": "literal" } ] diff --git a/adapters/adnuntius/params_test.go b/adapters/adnuntius/params_test.go index c3b42018340..259b5145801 100644 --- a/adapters/adnuntius/params_test.go +++ b/adapters/adnuntius/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adnuntius.json diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index ce0f78e8f00..aa9446d76f0 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -14,14 +14,14 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -const adapterVersion = "1.2.0" +const adapterVersion = "1.3.0" const maxUriLength = 8000 const measurementCode = ` %s", respData.AdQLib, respData.Tag), + ADomain: respData.ADomains, + CrID: fmt.Sprintf("%d", respData.CrID), + W: width, + H: height, + }, + BidType: respData.MediaType.Name, + }) + + return bidResponse, nil +} + +func buildHeaders(bidReq *openrtb2.BidRequest) http.Header { + headers := http.Header{} + + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if bidReq.Device != nil && len(bidReq.Device.IP) > 0 { + headers.Add("X-Forwarded-For", bidReq.Device.IP) + } + + return headers +} + +func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_ext.ImpExtAdQuery) *BidderRequest { + userId := "" + if bidReq.User != nil { + userId = bidReq.User.ID + } + + bidderRequest := &BidderRequest{ + V: prebidVersion, + PlacementCode: ext.PlacementID, + AuctionId: "", + BidType: ext.Type, + AdUnitCode: imp.TagID, + BidQid: userId, + BidId: fmt.Sprintf("%s%s", bidReq.ID, imp.ID), + Bidder: bidderName, + BidderRequestId: bidReq.ID, + BidRequestsCount: 1, + BidderRequestsCount: 1, + Sizes: getImpSizes(imp), + } + + if bidReq.Device != nil { + bidderRequest.BidIp = bidReq.Device.IP + bidderRequest.BidIpv6 = bidReq.Device.IPv6 + bidderRequest.BidUa = bidReq.Device.UA + } + + if bidReq.Site != nil { + bidderRequest.BidPageUrl = bidReq.Site.Page + } + + return bidderRequest +} + +func parseExt(ext json.RawMessage) (*openrtb_ext.ImpExtAdQuery, error) { + var bext adapters.ExtImpBidder + err := json.Unmarshal(ext, &bext) + if err != nil { + return nil, err + } + + var adsExt openrtb_ext.ImpExtAdQuery + err = json.Unmarshal(bext.Bidder, &adsExt) + if err != nil { + return nil, err + } + + // not validating, because it should have been done earlier by the server + return &adsExt, nil +} + +func parseResponseJson(respBody []byte) (*ResponseData, float64, int64, int64, []error) { + var response ResponseAdQuery + if err := json.Unmarshal(respBody, &response); err != nil { + return nil, 0, 0, 0, []error{err} + } + + if response.Data == nil { + return nil, 0, 0, 0, nil + } + + var errs []error + price, err := strconv.ParseFloat(response.Data.CPM, 64) + if err != nil { + errs = append(errs, err) + } + width, err := strconv.ParseInt(response.Data.MediaType.Width, 10, 64) + if err != nil { + errs = append(errs, err) + } + height, err := strconv.ParseInt(response.Data.MediaType.Height, 10, 64) + if err != nil { + errs = append(errs, err) + } + + if response.Data.MediaType.Name != openrtb_ext.BidTypeBanner { + return nil, 0, 0, 0, []error{fmt.Errorf("unsupported MediaType: %s", response.Data.MediaType.Name)} + } + + if len(errs) > 0 { + return nil, 0, 0, 0, errs + } + return response.Data, price, width, height, nil +} + +func getImpSizes(imp *openrtb2.Imp) string { + if imp.Banner == nil { + return "" + } + + if len(imp.Banner.Format) > 0 { + sizes := make([]string, len(imp.Banner.Format)) + for i, format := range imp.Banner.Format { + sizes[i] = strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10) + } + + return strings.Join(sizes, ",") + } + + if imp.Banner.W != nil && imp.Banner.H != nil { + return strconv.FormatInt(*imp.Banner.W, 10) + "x" + strconv.FormatInt(*imp.Banner.H, 10) + } + + return "" +} diff --git a/adapters/adquery/adquery_test.go b/adapters/adquery/adquery_test.go new file mode 100644 index 00000000000..792e7c553a8 --- /dev/null +++ b/adapters/adquery/adquery_test.go @@ -0,0 +1,21 @@ +package adquery + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdquery, config.Adapter{ + Endpoint: "https://bidder2.adquery.io/prebid/bid"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 902, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adquerytest", bidder) +} diff --git a/adapters/adquery/adquerytest/exemplary/empty.json b/adapters/adquery/adquerytest/exemplary/empty.json new file mode 100644 index 00000000000..055d9829e63 --- /dev/null +++ b/adapters/adquery/adquerytest/exemplary/empty.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "xyz" + }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [], + "bidder": "adquery" + }, + "httpCalls": [], + "expectedBidResponses": [] +} diff --git a/adapters/adquery/adquerytest/exemplary/many-imps.json b/adapters/adquery/adquerytest/exemplary/many-imps.json new file mode 100644 index 00000000000..b3f5f8c9a78 --- /dev/null +++ b/adapters/adquery/adquerytest/exemplary/many-imps.json @@ -0,0 +1,240 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + }, + { + "id": "2", + "tagid": "test-banner-imp-id-2", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f898", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + }, + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id-2", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc2", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f898", + "sizes": "300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc2", + "emission_id": "22e26bd9a702bc2", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + }, + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc2", + "impid": "2", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/exemplary/no-currency.json b/adapters/adquery/adquerytest/exemplary/no-currency.json new file mode 100644 index 00000000000..301eec783fa --- /dev/null +++ b/adapters/adquery/adquerytest/exemplary/no-currency.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "PLN", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/exemplary/ok.json b/adapters/adquery/adquerytest/exemplary/ok.json new file mode 100644 index 00000000000..573fb2336e6 --- /dev/null +++ b/adapters/adquery/adquerytest/exemplary/ok.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ipv6": "2001:4860:4860::8888", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "2001:4860:4860::8888", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json new file mode 100644 index 00000000000..5b2a54a70d3 --- /dev/null +++ b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "w": 320, + "h": 100 + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "PLN", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/data-null.json b/adapters/adquery/adquerytest/supplemental/data-null.json new file mode 100644 index 00000000000..8bbe8dc501f --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/data-null.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": null + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json new file mode 100644 index 00000000000..a9664e5232e --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc", + "emission_id": "22e26bd9a702bc", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "PLN", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320px", + "height": "50px", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "$4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "strconv.ParseFloat: parsing \"$4.14\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"320px\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"50px\": invalid syntax", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/malformed-ext.json b/adapters/adquery/adquerytest/supplemental/malformed-ext.json new file mode 100644 index 00000000000..46aaaed431d --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/malformed-ext.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": [], + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "expectedMakeRequestsErrors": [{ + "value": "json: cannot unmarshal array into Go struct field ImpExtAdQuery.placementId of type string", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/adquery/adquerytest/supplemental/malformed-resp.json b/adapters/adquery/adquerytest/supplemental/malformed-resp.json new file mode 100644 index 00000000000..8ab1763c0be --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/malformed-resp.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc", + "emission_id": "22e26bd9a702bc", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": "string-identifier", + "currency": "PLN", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go struct field ResponseData.data.creationId of type int64", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json new file mode 100644 index 00000000000..47b05d836f9 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc", + "emission_id": "22e26bd9a702bc", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "PLN", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "unknown", + "type": "unknown320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: unknown", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-video.json b/adapters/adquery/adquerytest/supplemental/mediatype-video.json new file mode 100644 index 00000000000..a6d19e7fc5a --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/mediatype-video.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc", + "emission_id": "22e26bd9a702bc", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "PLN", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "video", + "type": "video320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: video", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-device.json b/adapters/adquery/adquerytest/supplemental/no-device.json new file mode 100644 index 00000000000..9876cd57c88 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-device.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "http://www.example.com" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "http://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json new file mode 100644 index 00000000000..c745c9a7c30 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": {}, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": null + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json new file mode 100644 index 00000000000..f7de622587f --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": null + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-site.json b/adapters/adquery/adquerytest/supplemental/no-site.json new file mode 100644 index 00000000000..20305f52b11 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-site.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-bad-request.json b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json new file mode 100644 index 00000000000..f5e807ebad5 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 400, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-no-content.json b/adapters/adquery/adquerytest/supplemental/resp-no-content.json new file mode 100644 index 00000000000..1ce24c17081 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/resp-no-content.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 204, + "headers": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-server-error.json b/adapters/adquery/adquerytest/supplemental/resp-server-error.json new file mode 100644 index 00000000000..2e8e42ba927 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/resp-server-error.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + } + }, + "mockResponse": { + "status": 500, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/params_test.go b/adapters/adquery/params_test.go new file mode 100644 index 00000000000..e72b0b02ea3 --- /dev/null +++ b/adapters/adquery/params_test.go @@ -0,0 +1,49 @@ +package adquery + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdquery, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdquery, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "type": "banner300x250"}`, +} + +var invalidParams = []string{ + `{}`, + `{"placementId": 42}`, + `{"type": 3}`, + `{"placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897"}`, + `{"type": "banner"}`, + `{"placementId": 42, "type": "banner"}`, + `{"placementId": "too_short", "type": "banner"}`, + `{"placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "type": ""}`, +} diff --git a/adapters/adquery/types.go b/adapters/adquery/types.go new file mode 100644 index 00000000000..dab9619a7f5 --- /dev/null +++ b/adapters/adquery/types.go @@ -0,0 +1,45 @@ +package adquery + +import "github.com/prebid/prebid-server/v2/openrtb_ext" + +type BidderRequest struct { + V string `json:"v"` + PlacementCode string `json:"placementCode"` + AuctionId string `json:"auctionId,omitempty"` + BidType string `json:"type"` + AdUnitCode string `json:"adUnitCode"` + BidQid string `json:"bidQid"` + BidId string `json:"bidId"` + BidIp string `json:"bidIp"` + BidIpv6 string `json:"bidIpv6"` + BidUa string `json:"bidUa"` + Bidder string `json:"bidder"` + BidPageUrl string `json:"bidPageUrl"` + BidderRequestId string `json:"bidderRequestId"` + BidRequestsCount int `json:"bidRequestsCount"` + BidderRequestsCount int `json:"bidderRequestsCount"` + Sizes string `json:"sizes"` +} + +type ResponseAdQuery struct { + Data *ResponseData `json:"data"` +} + +type ResponseData struct { + ReqID string `json:"requestId"` + CrID int64 `json:"creationId"` + Currency string `json:"currency"` + CPM string `json:"cpm"` + Code string `json:"code"` + AdQLib string `json:"adqLib"` + Tag string `json:"tag"` + ADomains []string `json:"adDomains"` + DealID string `json:"dealid"` + MediaType AdQueryMediaType `json:"mediaType"` +} + +type AdQueryMediaType struct { + Name openrtb_ext.BidType `json:"name"` + Width string `json:"width"` + Height string `json:"height"` +} diff --git a/adapters/adrino/adrino.go b/adapters/adrino/adrino.go index a63ad9beef6..44de44f5cf8 100644 --- a/adapters/adrino/adrino.go +++ b/adapters/adrino/adrino.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adrino/adrino_test.go b/adapters/adrino/adrino_test.go index 7566f3ed499..e969868c135 100644 --- a/adapters/adrino/adrino_test.go +++ b/adapters/adrino/adrino_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adrino/params_test.go b/adapters/adrino/params_test.go index f82f08ce9e0..0ad36e6e4d6 100644 --- a/adapters/adrino/params_test.go +++ b/adapters/adrino/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adrino.json diff --git a/adapters/adsinteractive/adsinteractive.go b/adapters/adsinteractive/adsinteractive.go index 04edf774b80..d50dad0aabd 100644 --- a/adapters/adsinteractive/adsinteractive.go +++ b/adapters/adsinteractive/adsinteractive.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adsinteractive/adsinteractive_test.go b/adapters/adsinteractive/adsinteractive_test.go index 9a1397b799f..bed577c6003 100644 --- a/adapters/adsinteractive/adsinteractive_test.go +++ b/adapters/adsinteractive/adsinteractive_test.go @@ -3,9 +3,9 @@ package adsinteractive import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "adsinteractivetest" diff --git a/adapters/adsinteractive/params_test.go b/adapters/adsinteractive/params_test.go index 2561fc864da..caff03a2697 100644 --- a/adapters/adsinteractive/params_test.go +++ b/adapters/adsinteractive/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adsinteractive.json diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 00f797eccf8..ccd362830fc 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdtargetAdapter struct { diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go index 2ee45041b09..2813ea2c195 100644 --- a/adapters/adtarget/adtarget_test.go +++ b/adapters/adtarget/adtarget_test.go @@ -3,9 +3,9 @@ package adtarget import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go index 4c39639fb7b..d0993215086 100644 --- a/adapters/adtarget/params_test.go +++ b/adapters/adtarget/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtarget.json diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index e2f5ef82cab..281d79233a0 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdtelligentAdapter struct { diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 948710387b3..905ce013840 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -3,9 +3,9 @@ package adtelligent import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtelligent/params_test.go b/adapters/adtelligent/params_test.go index 227920b25b4..f86a7641af9 100644 --- a/adapters/adtelligent/params_test.go +++ b/adapters/adtelligent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtelligent.json diff --git a/adapters/adtrgtme/adtrgtme.go b/adapters/adtrgtme/adtrgtme.go index 254bf5051e9..47feaceefa7 100644 --- a/adapters/adtrgtme/adtrgtme.go +++ b/adapters/adtrgtme/adtrgtme.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adtrgtme/adtrgtme_test.go b/adapters/adtrgtme/adtrgtme_test.go index 91d9b233ffe..07bfea3c652 100644 --- a/adapters/adtrgtme/adtrgtme_test.go +++ b/adapters/adtrgtme/adtrgtme_test.go @@ -3,9 +3,9 @@ package adtrgtme import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtrgtme/params_test.go b/adapters/adtrgtme/params_test.go index e89f8423ed4..4745c323887 100644 --- a/adapters/adtrgtme/params_test.go +++ b/adapters/adtrgtme/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 19bb4c326e2..c779483d64a 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AdvangelistsAdapter struct { diff --git a/adapters/advangelists/advangelists_test.go b/adapters/advangelists/advangelists_test.go index e4c5debaa79..5165ef1f3a7 100644 --- a/adapters/advangelists/advangelists_test.go +++ b/adapters/advangelists/advangelists_test.go @@ -3,9 +3,9 @@ package advangelists import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/advangelists/params_test.go b/adapters/advangelists/params_test.go index a58217a0ffd..966967ba312 100644 --- a/adapters/advangelists/params_test.go +++ b/adapters/advangelists/params_test.go @@ -2,8 +2,9 @@ package advangelists import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go index d2181b6591a..9937b6ae3ee 100644 --- a/adapters/adview/adview.go +++ b/adapters/adview/adview.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/adview/adview_test.go b/adapters/adview/adview_test.go index d0c993cfb56..2045586e97d 100644 --- a/adapters/adview/adview_test.go +++ b/adapters/adview/adview_test.go @@ -3,9 +3,9 @@ package adview import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adview/params_test.go b/adapters/adview/params_test.go index 6d124e9b556..d5e498645e0 100644 --- a/adapters/adview/params_test.go +++ b/adapters/adview/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go index 008743914df..a8cb380f6c0 100644 --- a/adapters/adxcg/adxcg.go +++ b/adapters/adxcg/adxcg.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. @@ -84,13 +84,13 @@ func (adapter *adapter) MakeBids( return nil, []error{err} } - bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid) - bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(openRTBRequest.Imp)) + bidderResponse.Currency = openRTBBidderResponse.Cur var typedBid *adapters.TypedBid for _, seatBid := range openRTBBidderResponse.SeatBid { for _, bid := range seatBid.Bid { activeBid := bid - bidType, err := getMediaTypeForImp(activeBid.ImpID, openRTBRequest.Imp) + bidType, err := getReturnTypeFromMtypeForImp(activeBid.MType) if err != nil { errs = append(errs, err) continue @@ -105,21 +105,17 @@ func (adapter *adapter) MakeBids( } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil - } else if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } else if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } - - } - } - - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), +func getReturnTypeFromMtypeForImp(mType openrtb2.MarkupType) (openrtb_ext.BidType, error) { + switch mType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + default: + return "", &errortypes.BadServerResponse{Message: "Unsupported return type"} } } diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go index aa5f955c372..f117f7b2ba1 100644 --- a/adapters/adxcg/adxcg_test.go +++ b/adapters/adxcg/adxcg_test.go @@ -3,9 +3,9 @@ package adxcg import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "adxcgtest" diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json new file mode 100644 index 00000000000..8678a62c8a7 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "cur": ["EUR"], + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "cur": ["EUR"], + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "adxcg", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype":1 + }] + }], + "cur": "EUR" + } + } + }], + + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json index 85ed18ff1a3..42902014126 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json @@ -55,7 +55,8 @@ "cid": "987", "crid": "12345678", "h": 250, - "w": 300 + "w": 300, + "mtype":1 }] }], "cur": "USD" @@ -75,7 +76,8 @@ "cid": "987", "crid": "12345678", "w": 300, - "h": 250 + "h": 250, + "mtype":1 }, "type": "banner" }] diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-native.json b/adapters/adxcg/adxcgtest/exemplary/simple-native.json index 1a449e601a2..1b0d5b5996c 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-native.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-native.json @@ -42,7 +42,8 @@ "price": 1.16, "adm": "native-ad", "w": 1, - "h": 1 + "h": 1, + "mtype":4 } ] } @@ -63,7 +64,8 @@ "price": 1.16, "adm": "native-ad", "w": 1, - "h": 1 + "h": 1, + "mtype":4 }, "type": "native" } diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-video.json b/adapters/adxcg/adxcgtest/exemplary/simple-video.json index b5f54b9bc44..735f10036b8 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-video.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-video.json @@ -74,7 +74,8 @@ "price": 1.16, "adm": "some-test-ad", "w": 300, - "h": 250 + "h": 250, + "mtype":2 } ] } @@ -95,7 +96,8 @@ "price": 1.16, "adm": "some-test-ad", "w": 300, - "h": 250 + "h": 250, + "mtype":2 }, "type": "video" } diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index e00e95dccb5..72e6612b675 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go index d3000f673fc..2cf7a2b49ec 100644 --- a/adapters/adyoulike/adyoulike_test.go +++ b/adapters/adyoulike/adyoulike_test.go @@ -3,9 +3,9 @@ package adyoulike import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go index 8aebaf2844e..6eb9e09a3cb 100644 --- a/adapters/adyoulike/params_test.go +++ b/adapters/adyoulike/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/adyoulike.json diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 5a8afb00045..178c7f6ca68 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type AJAAdapter struct { diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go index 75e35bedeb0..de4f1d13dab 100644 --- a/adapters/aja/aja_test.go +++ b/adapters/aja/aja_test.go @@ -3,9 +3,9 @@ package aja import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go index 07f2b123389..e646467f744 100644 --- a/adapters/algorix/algorix.go +++ b/adapters/algorix/algorix.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/algorix/algorix_test.go b/adapters/algorix/algorix_test.go index 762b00dcee4..a401713d290 100644 --- a/adapters/algorix/algorix_test.go +++ b/adapters/algorix/algorix_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/algorix/params_test.go b/adapters/algorix/params_test.go index 227b9e0a6d4..7017a43c730 100644 --- a/adapters/algorix/params_test.go +++ b/adapters/algorix/params_test.go @@ -2,8 +2,9 @@ package algorix import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/alkimi/alkimi.go b/adapters/alkimi/alkimi.go new file mode 100644 index 00000000000..f0979c3a809 --- /dev/null +++ b/adapters/alkimi/alkimi.go @@ -0,0 +1,192 @@ +package alkimi + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/floors" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +const price_macro = "${AUCTION_PRICE}" + +type adapter struct { + endpoint string +} + +type extObj struct { + AlkimiBidderExt openrtb_ext.ExtImpAlkimi `json:"bidder"` +} + +// Builder builds a new instance of the Alkimi adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + endpointURL, err := url.Parse(config.Endpoint) + if err != nil || len(endpointURL.String()) == 0 { + return nil, fmt.Errorf("invalid endpoint: %v", err) + } + + bidder := &adapter{ + endpoint: endpointURL.String(), + } + return bidder, nil +} + +// MakeRequests creates Alkimi adapter requests +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { + reqCopy := *request + + updated, errs := updateImps(reqCopy) + if len(errs) > 0 || len(reqCopy.Imp) != len(updated) { + return nil, errs + } + + reqCopy.Imp = updated + encoded, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + } else { + reqBidder := buildBidderRequest(adapter, encoded) + reqsBidder = append(reqsBidder, reqBidder) + } + return +} + +func updateImps(bidRequest openrtb2.BidRequest) ([]openrtb2.Imp, []error) { + var errs []error + + updatedImps := make([]openrtb2.Imp, 0, len(bidRequest.Imp)) + for _, imp := range bidRequest.Imp { + + var bidderExt adapters.ExtImpBidder + var extImpAlkimi openrtb_ext.ExtImpAlkimi + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + + if err := json.Unmarshal(bidderExt.Bidder, &extImpAlkimi); err != nil { + errs = append(errs, err) + continue + } + + var bidFloorPrice floors.Price + bidFloorPrice.FloorMinCur = imp.BidFloorCur + bidFloorPrice.FloorMin = imp.BidFloor + + if len(bidFloorPrice.FloorMinCur) > 0 && bidFloorPrice.FloorMin > 0 { + imp.BidFloor = bidFloorPrice.FloorMin + } else { + imp.BidFloor = extImpAlkimi.BidFloor + } + imp.Instl = extImpAlkimi.Instl + imp.Exp = extImpAlkimi.Exp + + temp := extObj{AlkimiBidderExt: extImpAlkimi} + temp.AlkimiBidderExt.AdUnitCode = imp.ID + + extJson, err := json.Marshal(temp) + if err != nil { + errs = append(errs, err) + continue + } + imp.Ext = extJson + updatedImps = append(updatedImps, imp) + } + + return updatedImps, errs +} + +func buildBidderRequest(adapter *adapter, encoded []byte) *adapters.RequestData { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + reqBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: encoded, + Headers: headers, + } + return reqBidder +} + +// MakeBids will parse the bids from the Alkimi server +func (adapter *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + err := json.Unmarshal(response.Body, &bidResp) + if err != nil { + return nil, []error{err} + } + + seatBidCount := len(bidResp.SeatBid) + if seatBidCount == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + copyBid := bid + resolveMacros(©Bid) + impId := copyBid.ImpID + imp := request.Imp + bidType, err := getMediaTypeForImp(impId, imp) + if err != nil { + errs = append(errs, err) + continue + } + bidderBid := &adapters.TypedBid{ + Bid: ©Bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, bidderBid) + } + } + return bidResponse, errs +} + +func resolveMacros(bid *openrtb2.Bid) { + strPrice := strconv.FormatFloat(bid.Price, 'f', -1, 64) + bid.NURL = strings.Replace(bid.NURL, price_macro, strPrice, -1) + bid.AdM = strings.Replace(bid.AdM, price_macro, strPrice, -1) +} + +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find imp \"%s\"", impId), + } +} diff --git a/adapters/alkimi/alkimi_test.go b/adapters/alkimi/alkimi_test.go new file mode 100644 index 00000000000..b745f0feb95 --- /dev/null +++ b/adapters/alkimi/alkimi_test.go @@ -0,0 +1,57 @@ +package alkimi + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +const ( + alkimiTestEndpoint = "https://exchange.alkimi-onboarding.com/server/bid" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "alkimitest", bidder) +} + +func TestEndpointEmpty(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: ""}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestEndpointMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: " http://leading.space.is.invalid"}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestBuilder(t *testing.T) { + bidder, buildErr := buildBidder() + if buildErr != nil { + t.Fatalf("Failed to build bidder: %v", buildErr) + } + assert.NotNil(t, bidder) +} + +func buildBidder() (adapters.Bidder, error) { + return Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) +} diff --git a/adapters/alkimi/alkimitest/exemplary/simple-audio.json b/adapters/alkimi/alkimitest/exemplary/simple-audio.json new file mode 100644 index 00000000000..09ac2131a12 --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-audio.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + } + ], + "seat": "alkimi" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + }, + "type": "audio" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/alkimi/alkimitest/exemplary/simple-banner.json b/adapters/alkimi/alkimitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..96512989596 --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-banner.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 0.5, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "
<\/a><\/div>
", + "adm": "
", "id": "some_test_ad_id_1", "impid": "some_test_ad_id_1", "ttl": 300, @@ -168,7 +168,7 @@ { "bids": [{ "bid": { - "adm": "
", + "adm": "
", "id": "some_test_ad_id_1", "impid": "some_test_ad_id_1", "crid": "94395500", diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/banner-and-video-site.json similarity index 69% rename from adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/banner-and-video-site.json index f47fd66784e..1ea0397fced 100644 --- a/adapters/emx_digital/emx_digitaltest/exemplary/banner-and-video-site.json +++ b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/banner-and-video-site.json @@ -136,7 +136,7 @@ "seatbid": [{ "seat": "12356", "bid": [{ - "adm": "
", + "adm": "
", "id": "some_test_ad_id_1", "impid": "some_test_ad_id_1", "ttl": 300, @@ -170,7 +170,7 @@ "expectedBidResponses": [{ "bids": [{ "bid": { - "adm": "
", + "adm": "
", "id": "some_test_ad_id_1", "impid": "some_test_ad_id_1", "crid": "94395500", diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/banner-app.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/exemplary/banner-app.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/banner-app.json diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/minimal-banner.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/exemplary/minimal-banner.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/minimal-banner.json diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-app.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-app.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/exemplary/video-app.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-app.json diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-ctv.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-ctv.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/exemplary/video-ctv.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-ctv.json diff --git a/adapters/emx_digital/emx_digitaltest/exemplary/video-site.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-site.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/exemplary/video-site.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/exemplary/video-site.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/add-bidfloor.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/add-bidfloor.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/add-bidfloor.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/app-domain-and-url-correctly-parsed.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/app-domain-and-url-correctly-parsed.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/app-domain-and-url-correctly-parsed.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/app-domain-and-url-correctly-parsed.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/app-storeUrl-correctly-parsed.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/app-storeUrl-correctly-parsed.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/app-storeUrl-correctly-parsed.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/app-storeUrl-correctly-parsed.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-banner-missing-sizes.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-banner-missing-sizes.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-banner-missing-sizes.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-ext-tagid-value.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-ext-tagid-value.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-ext-tagid-value.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-mimes.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-missing-mimes.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-mimes.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-missing-mimes.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-sizes.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-missing-sizes.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/bad-imp-video-missing-sizes.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-missing-sizes.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/build-banner-object.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/build-banner-object.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/build-banner-object.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/build-video-object.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/build-video-object.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/build-video-object.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/build-video-object.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-request-no-banner.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-request-no-banner.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/invalid-request-no-banner.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-request-no-banner.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-response-no-bids.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-no-bids.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-response-no-bids.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-response-unmarshall-error.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/invalid-response-unmarshall-error.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/invalid-response-unmarshall-error.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/no-imps-in-request.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/no-imps-in-request.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/no-imps-in-request.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/server-error-code.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/server-error-code.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/server-error-code.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/server-no-content.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/server-no-content.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/server-no-content.json diff --git a/adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/site-domain-and-url-correctly-parsed.json similarity index 100% rename from adapters/emx_digital/emx_digitaltest/supplemental/site-domain-and-url-correctly-parsed.json rename to adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/site-domain-and-url-correctly-parsed.json diff --git a/adapters/emx_digital/emx_digital.go b/adapters/cadent_aperture_mx/cadentaperturemx.go similarity index 82% rename from adapters/emx_digital/emx_digital.go rename to adapters/cadent_aperture_mx/cadentaperturemx.go index 1b38263c6ff..4b4ccc6b2e4 100644 --- a/adapters/emx_digital/emx_digital.go +++ b/adapters/cadent_aperture_mx/cadentaperturemx.go @@ -1,4 +1,4 @@ -package emx_digital +package cadentaperturemx import ( "encoding/json" @@ -11,13 +11,13 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -type EmxDigitalAdapter struct { +type adapter struct { endpoint string testing bool } @@ -33,7 +33,7 @@ func buildEndpoint(endpoint string, testing bool, timeout int64) string { return endpoint + "?t=" + strconv.FormatInt(timeout, 10) + "&ts=" + strconv.FormatInt(time.Now().Unix(), 10) + "&src=pbserver" } -func (a *EmxDigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error if len(request.Imp) == 0 { @@ -81,7 +81,7 @@ func (a *EmxDigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * }}, errs } -func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpEmxDigital, error) { +func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpCadentApertureMX, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -89,27 +89,27 @@ func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpEmxDigital, error) { } } - var emxExt openrtb_ext.ExtImpEmxDigital - if err := json.Unmarshal(bidderExt.Bidder, &emxExt); err != nil { + var cadentExt openrtb_ext.ExtImpCadentApertureMX + if err := json.Unmarshal(bidderExt.Bidder, &cadentExt); err != nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID), } } - tagIDValidation, err := strconv.ParseInt(emxExt.TagID, 10, 64) + tagIDValidation, err := strconv.ParseInt(cadentExt.TagID, 10, 64) if err != nil || tagIDValidation == 0 { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID), } } - if emxExt.TagID == "" { + if cadentExt.TagID == "" { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Ignoring imp id=%s, no tagid present", imp.ID), } } - return &emxExt, nil + return &cadentExt, nil } func buildImpBanner(imp *openrtb2.Imp) error { @@ -175,13 +175,13 @@ func cleanProtocol(protocols []adcom1.MediaCreativeSubtype) []adcom1.MediaCreati return newitems } -// Add EMX required properties to Imp object -func addImpProps(imp *openrtb2.Imp, secure *int8, emxExt *openrtb_ext.ExtImpEmxDigital) { - imp.TagID = emxExt.TagID +// Add Cadent required properties to Imp object +func addImpProps(imp *openrtb2.Imp, secure *int8, cadentExt *openrtb_ext.ExtImpCadentApertureMX) { + imp.TagID = cadentExt.TagID imp.Secure = secure - if emxExt.BidFloor != "" { - bidFloor, err := strconv.ParseFloat(emxExt.BidFloor, 64) + if cadentExt.BidFloor != "" { + bidFloor, err := strconv.ParseFloat(cadentExt.BidFloor, 64) if err != nil { bidFloor = 0 } @@ -202,7 +202,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -// Handle request errors and formatting to be sent to EMX +// Handle request errors and formatting to be sent to Cadent func preprocess(request *openrtb2.BidRequest) []error { impsCount := len(request.Imp) errors := make([]error, 0, impsCount) @@ -225,13 +225,13 @@ func preprocess(request *openrtb2.BidRequest) []error { } for _, imp := range request.Imp { - emxExt, err := unpackImpExt(&imp) + cadentExt, err := unpackImpExt(&imp) if err != nil { errors = append(errors, err) continue } - addImpProps(&imp, &secure, emxExt) + addImpProps(&imp, &secure, cadentExt) if imp.Video != nil { if err := buildImpVideo(&imp); err != nil { @@ -253,7 +253,7 @@ func preprocess(request *openrtb2.BidRequest) []error { } // MakeBids make the bids for the bid response. -func (a *EmxDigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { // no bid response @@ -309,9 +309,9 @@ func ContainsAny(raw string, keys []string) bool { } -// Builder builds a new instance of the EmxDigital adapter for the given bidder with the given config. +// Builder builds a new instance of the Cadent Aperture MX adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &EmxDigitalAdapter{ + bidder := &adapter{ endpoint: config.Endpoint, testing: false, } diff --git a/adapters/cadent_aperture_mx/cadentaperturemx_test.go b/adapters/cadent_aperture_mx/cadentaperturemx_test.go new file mode 100644 index 00000000000..961459a2ea8 --- /dev/null +++ b/adapters/cadent_aperture_mx/cadentaperturemx_test.go @@ -0,0 +1,27 @@ +package cadentaperturemx + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderCadentApertureMX, config.Adapter{ + Endpoint: "https://hb.emxdgt.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + setTesting(bidder) + adapterstest.RunJSONBidderTest(t, "cadent_aperture_mxtest", bidder) +} + +func setTesting(bidder adapters.Bidder) { + bidderCadentApertureMX, _ := bidder.(*adapter) + bidderCadentApertureMX.testing = true +} diff --git a/adapters/emx_digital/params_test.go b/adapters/cadent_aperture_mx/params_test.go similarity index 71% rename from adapters/emx_digital/params_test.go rename to adapters/cadent_aperture_mx/params_test.go index 49ad9eb1a9c..6691537d02c 100644 --- a/adapters/emx_digital/params_test.go +++ b/adapters/cadent_aperture_mx/params_test.go @@ -1,10 +1,10 @@ -package emx_digital +package cadentaperturemx import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { @@ -14,8 +14,8 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderEmxDigital, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected emx_digital params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderCadentApertureMX, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected cadent_aperture_mx params: %s", validParam) } } } @@ -27,7 +27,7 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderEmxDigital, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderCadentApertureMX, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } diff --git a/adapters/ccx/ccx.go b/adapters/ccx/ccx.go index 09b89b2c9af..ef5a6a17a9a 100644 --- a/adapters/ccx/ccx.go +++ b/adapters/ccx/ccx.go @@ -6,11 +6,11 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" ) type adapter struct { diff --git a/adapters/ccx/ccx_test.go b/adapters/ccx/ccx_test.go index 4d02c9848fd..86f85dc6eb7 100644 --- a/adapters/ccx/ccx_test.go +++ b/adapters/ccx/ccx_test.go @@ -3,9 +3,9 @@ package ccx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/ccx/params_test.go b/adapters/ccx/params_test.go index ecd9421333b..cb500fec509 100644 --- a/adapters/ccx/params_test.go +++ b/adapters/ccx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/coinzilla/coinzilla.go b/adapters/coinzilla/coinzilla.go index 039f3ecaf25..bc04758e537 100644 --- a/adapters/coinzilla/coinzilla.go +++ b/adapters/coinzilla/coinzilla.go @@ -6,11 +6,11 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/errortypes" ) func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { diff --git a/adapters/coinzilla/coinzilla_test.go b/adapters/coinzilla/coinzilla_test.go index 6c79a2b12d1..39b3bb1f6e5 100644 --- a/adapters/coinzilla/coinzilla_test.go +++ b/adapters/coinzilla/coinzilla_test.go @@ -3,9 +3,9 @@ package coinzilla import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "coinzillatest" diff --git a/adapters/coinzilla/params_test.go b/adapters/coinzilla/params_test.go index fa24b144769..dd2cd757496 100644 --- a/adapters/coinzilla/params_test.go +++ b/adapters/coinzilla/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/colossus/colossus.go b/adapters/colossus/colossus.go index 65dd5c5dc9d..bc9836984ce 100644 --- a/adapters/colossus/colossus.go +++ b/adapters/colossus/colossus.go @@ -7,10 +7,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ColossusAdapter struct { diff --git a/adapters/colossus/colossus_test.go b/adapters/colossus/colossus_test.go index 3b0f0fc5efe..6298b7f3958 100644 --- a/adapters/colossus/colossus_test.go +++ b/adapters/colossus/colossus_test.go @@ -3,9 +3,9 @@ package colossus import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/colossus/params_test.go b/adapters/colossus/params_test.go index 09fea5981c6..ebafd637d6a 100644 --- a/adapters/colossus/params_test.go +++ b/adapters/colossus/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the colossus schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/compass/compass.go b/adapters/compass/compass.go index 460d5307b45..3edf9b6f157 100644 --- a/adapters/compass/compass.go +++ b/adapters/compass/compass.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/compass/compass_test.go b/adapters/compass/compass_test.go index ad7a4ecf61b..08d9d1bb46e 100644 --- a/adapters/compass/compass_test.go +++ b/adapters/compass/compass_test.go @@ -3,9 +3,9 @@ package compass import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/compass/params_test.go b/adapters/compass/params_test.go index fca398b06fb..37074bb40f7 100644 --- a/adapters/compass/params_test.go +++ b/adapters/compass/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go index 1c33d6bf3c6..ee2e8eeb699 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ConnectAdAdapter struct { diff --git a/adapters/connectad/connectad_test.go b/adapters/connectad/connectad_test.go index 037ccbb0a3d..6ce9dd3eade 100644 --- a/adapters/connectad/connectad_test.go +++ b/adapters/connectad/connectad_test.go @@ -3,9 +3,9 @@ package connectad import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/connectad/params_test.go b/adapters/connectad/params_test.go index 6d55b1ce7d9..2ab3c4902e7 100644 --- a/adapters/connectad/params_test.go +++ b/adapters/connectad/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/consumable/adtypes.go b/adapters/consumable/adtypes.go deleted file mode 100644 index 9974a4f26d6..00000000000 --- a/adapters/consumable/adtypes.go +++ /dev/null @@ -1,64 +0,0 @@ -package consumable - -import ( - "strconv" - - "github.com/prebid/openrtb/v19/openrtb2" -) - -/* Turn array of openrtb formats into consumable's code*/ -func getSizeCodes(Formats []openrtb2.Format) []int { - - codes := make([]int, 0) - for _, format := range Formats { - str := strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10) - if code, ok := sizeMap[str]; ok { - codes = append(codes, code) - } - } - return codes -} - -var sizeMap = map[string]int{ - "120x90": 1, - // 120x90 is in twice in prebid.js implementation - probably as spacer - "468x60": 3, - "728x90": 4, - "300x250": 5, - "160x600": 6, - "120x600": 7, - "300x100": 8, - "180x150": 9, - "336x280": 10, - "240x400": 11, - "234x60": 12, - "88x31": 13, - "120x60": 14, - "120x240": 15, - "125x125": 16, - "220x250": 17, - "250x250": 18, - "250x90": 19, - "0x0": 20, // TODO: can this be removed - I suspect it's padding in prebid.js impl - "200x90": 21, - "300x50": 22, - "320x50": 23, - "320x480": 24, - "185x185": 25, - "620x45": 26, - "300x125": 27, - "800x250": 28, - // below order is preserved from prebid.js implementation for easy comparison - "970x90": 77, - "970x250": 123, - "300x600": 43, - "970x66": 286, - "970x280": 3230, - "486x60": 429, - "700x500": 374, - "300x1050": 934, - "320x100": 1578, - "320x250": 331, - "320x267": 3301, - "728x250": 2730, -} diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go index 0e16497774c..932a58b5679 100644 --- a/adapters/consumable/consumable.go +++ b/adapters/consumable/consumable.go @@ -3,319 +3,144 @@ package consumable import ( "encoding/json" "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "net/http" ) -type ConsumableAdapter struct { - clock instant +type adapter struct { endpoint string } -type bidRequest struct { - Placements []placement `json:"placements"` - Time int64 `json:"time"` - NetworkId int `json:"networkId,omitempty"` - SiteId int `json:"siteId"` - UnitId int `json:"unitId"` - UnitName string `json:"unitName,omitempty"` - IncludePricingData bool `json:"includePricingData"` - User user `json:"user,omitempty"` - Referrer string `json:"referrer,omitempty"` - Ip string `json:"ip,omitempty"` - Url string `json:"url,omitempty"` - EnableBotFiltering bool `json:"enableBotFiltering,omitempty"` - Parallel bool `json:"parallel"` - CCPA string `json:"ccpa,omitempty"` - GDPR *bidGdpr `json:"gdpr,omitempty"` - Coppa bool `json:"coppa,omitempty"` - SChain openrtb2.SupplyChain `json:"schain"` - Content *openrtb2.Content `json:"content,omitempty"` - GPP string `json:"gpp,omitempty"` - GPPSID []int8 `json:"gpp_sid,omitempty"` -} - -type placement struct { - DivName string `json:"divName"` - NetworkId int `json:"networkId,omitempty"` - SiteId int `json:"siteId"` - UnitId int `json:"unitId"` - UnitName string `json:"unitName,omitempty"` - AdTypes []int `json:"adTypes"` -} - -type user struct { - Key string `json:"key,omitempty"` - Eids []openrtb2.EID `json:"eids,omitempty"` -} - -type bidGdpr struct { - Applies *bool `json:"applies,omitempty"` - Consent string `json:"consent,omitempty"` -} - -type bidResponse struct { - Decisions map[string]decision `json:"decisions"` // map by bidId -} - -/** - * See https://dev.adzerk.com/v1.0/reference/response - */ -type decision struct { - Pricing *pricing `json:"pricing"` - AdID int64 `json:"adId"` - BidderName string `json:"bidderName,omitempty"` - CreativeID string `json:"creativeId,omitempty"` - Contents []contents `json:"contents"` - ImpressionUrl *string `json:"impressionUrl,omitempty"` - Width uint64 `json:"width,omitempty"` // Consumable extension, not defined by Adzerk - Height uint64 `json:"height,omitempty"` // Consumable extension, not defined by Adzerk - Adomain []string `json:"adomain,omitempty"` - Cats []string `json:"cats,omitempty"` -} - -type contents struct { - Body string `json:"body"` -} - -type pricing struct { - ClearPrice *float64 `json:"clearPrice"` -} - -func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - headers := http.Header{ "Content-Type": {"application/json"}, "Accept": {"application/json"}, } - - if request.Device != nil { - if request.Device.UA != "" { - headers.Set("User-Agent", request.Device.UA) - } - - if request.Device.IP != "" { - headers.Set("Forwarded", "for="+request.Device.IP) - headers.Set("X-Forwarded-For", request.Device.IP) - } - } - - // Set azk cookie to one we got via sync - if request.User != nil { - userID := strings.TrimSpace(request.User.BuyerUID) - if len(userID) > 0 { - headers.Add("Cookie", fmt.Sprintf("%s=%s", "azk", userID)) - } - } - - if request.Site != nil && request.Site.Page != "" { - headers.Set("Referer", request.Site.Page) - - pageUrl, err := url.Parse(request.Site.Page) - if err != nil { - errs = append(errs, err) - } else { - origin := url.URL{ - Scheme: pageUrl.Scheme, - Opaque: pageUrl.Opaque, - Host: pageUrl.Host, - } - headers.Set("Origin", origin.String()) - } - } - - body := bidRequest{ - Placements: make([]placement, len(request.Imp)), - Time: a.clock.Now().Unix(), - IncludePricingData: true, - EnableBotFiltering: true, - Parallel: true, - } - - if request.Site != nil { - body.Referrer = request.Site.Ref // Effectively the previous page to the page where the ad will be shown - body.Url = request.Site.Page // where the impression will be made - } - - gdpr := bidGdpr{} - - ccpaPolicy, err := ccpa.ReadFromRequest(request) + bodyBytes, err := json.Marshal(request) if err != nil { - errs = append(errs, err) - } else { - body.CCPA = ccpaPolicy.Consent + return nil, []error{err} } - if request.Regs != nil && request.Regs.Ext != nil { - var extRegs openrtb_ext.ExtRegs - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { - errs = append(errs, err) - } else { - if extRegs.GDPR != nil { - applies := *extRegs.GDPR != 0 - gdpr.Applies = &applies - body.GDPR = &gdpr - } + if request.Site != nil { + _, consumableExt, err := extractExtensions(request.Imp[0]) + if err != nil { + return nil, err } - } - - if request.User != nil && request.User.Ext != nil { - var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { - errs = append(errs, err) - } else { - gdpr.Consent = extUser.Consent - body.GDPR = &gdpr - - if hasEids(extUser.Eids) { - body.User.Eids = extUser.Eids - } + if consumableExt.SiteId == 0 && consumableExt.NetworkId == 0 && consumableExt.UnitId == 0 { + return nil, []error{&errortypes.FailedToRequestBids{ + Message: "SiteId, NetworkId and UnitId are all required for site requests", + }} } - } - - if request.Source != nil && request.Source.Ext != nil { - var extSChain openrtb_ext.ExtRequestPrebidSChain - if err := json.Unmarshal(request.Source.Ext, &extSChain); err != nil { - errs = append(errs, err) - } else { - body.SChain = extSChain.SChain + requests := []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://e.serverbid.com/sb/rtb", + Body: bodyBytes, + Headers: headers, + }, } - } - - body.Coppa = request.Regs != nil && request.Regs.COPPA > 0 - - if request.Regs != nil && request.Regs.GPP != "" { - body.GPP = request.Regs.GPP - } - - if request.Regs != nil && request.Regs.GPPSID != nil { - body.GPPSID = request.Regs.GPPSID - } - - if request.Site != nil && request.Site.Content != nil { - body.Content = request.Site.Content - } else if request.App != nil && request.App.Content != nil { - body.Content = request.App.Content - } - - for i, impression := range request.Imp { - - _, consumableExt, err := extractExtensions(impression) + return requests, errs + } else { + _, consumableExt, err := extractExtensions(request.Imp[0]) if err != nil { return nil, err } - // These get set on the first one in observed working requests - if i == 0 { - body.NetworkId = consumableExt.NetworkId - body.SiteId = consumableExt.SiteId - body.UnitId = consumableExt.UnitId - body.UnitName = consumableExt.UnitName + if consumableExt.PlacementId == "" { + return nil, []error{&errortypes.FailedToRequestBids{ + Message: "PlacementId is required for non-site requests", + }} } - - body.Placements[i] = placement{ - DivName: impression.ID, - NetworkId: consumableExt.NetworkId, - SiteId: consumableExt.SiteId, - UnitId: consumableExt.UnitId, - UnitName: consumableExt.UnitName, - AdTypes: getSizeCodes(impression.Banner.Format), // was adTypes: bid.adTypes || getSize(bid.sizes) in prebid.js + requests := []*adapters.RequestData{ + { + Method: "POST", + Uri: "https://e.serverbid.com/rtb/bid?s=" + consumableExt.PlacementId, + Body: bodyBytes, + Headers: headers, + }, } + return requests, errs } - bodyBytes, err := json.Marshal(body) - if err != nil { - return nil, []error{err} - } - - requests := []*adapters.RequestData{ - { - Method: "POST", - Uri: "https://e.serverbid.com/api/v2", - Body: bodyBytes, - Headers: headers, - }, - } - - return requests, errs } - -/* -internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) -*/ -func (a *ConsumableAdapter) MakeBids( - internalRequest *openrtb2.BidRequest, - externalRequest *adapters.RequestData, - response *adapters.ResponseData, -) (*adapters.BidderResponse, []error) { - - if response.StatusCode == http.StatusNoContent { +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { return nil, nil } - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} } - var serverResponse bidResponse // response from Consumable - if err := json.Unmarshal(response.Body, &serverResponse); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("error while decoding response, err: %s", err), - }} + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} } - bidderResponse := adapters.NewBidderResponse() + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur var errors []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errors = append(errors, err) + continue + } + var bidVideo *openrtb_ext.ExtBidPrebidVideo + if bidType == openrtb_ext.BidTypeVideo { + bidVideo = &openrtb_ext.ExtBidPrebidVideo{Duration: int(bid.Dur)} + } + switch bidType { + case openrtb_ext.BidTypeAudio: + seatBid.Bid[i].MType = openrtb2.MarkupAudio + break + case openrtb_ext.BidTypeVideo: + seatBid.Bid[i].MType = openrtb2.MarkupVideo + break + case openrtb_ext.BidTypeBanner: + seatBid.Bid[i].MType = openrtb2.MarkupBanner + break + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + BidVideo: bidVideo, + }) + } + } + return bidResponse, nil +} - for impID, decision := range serverResponse.Decisions { +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.MType != 0 { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + } + } - if decision.Pricing != nil && decision.Pricing.ClearPrice != nil { - bid := openrtb2.Bid{} - bid.ID = internalRequest.ID - bid.ImpID = impID - bid.Price = *decision.Pricing.ClearPrice - bid.AdM = retrieveAd(decision) - bid.W = int64(decision.Width) - bid.H = int64(decision.Height) - bid.CrID = strconv.FormatInt(decision.AdID, 10) - bid.Exp = 30 // TODO: Check this is intention of TTL - bid.ADomain = decision.Adomain - bid.Cat = decision.Cats - // not yet ported from prebid.js adapter - //bid.requestId = bidId; - //bid.currency = 'USD'; - //bid.netRevenue = true; - //bid.referrer = utils.getTopWindowUrl(); + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), + } +} - bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - // Consumable units are always HTML, never VAST. - // From Prebid's point of view, this means that Consumable units - // are always "banners". - BidType: openrtb_ext.BidTypeBanner, - }) - } +// Builder builds a new instance of the Consumable adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, } - return bidderResponse, errors + return bidder, nil } func extractExtensions(impression openrtb2.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) { @@ -335,21 +160,3 @@ func extractExtensions(impression openrtb2.Imp) (*adapters.ExtImpBidder, *openrt return &bidderExt, &consumableExt, nil } - -// Builder builds a new instance of the Consumable adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &ConsumableAdapter{ - clock: realInstant{}, - endpoint: config.Endpoint, - } - return bidder, nil -} - -func hasEids(eids []openrtb2.EID) bool { - for i := 0; i < len(eids); i++ { - if len(eids[i].UIDs) > 0 && eids[i].UIDs[0].ID != "" { - return true - } - } - return false -} diff --git a/adapters/consumable/consumable/exemplary/app-audio.json b/adapters/consumable/consumable/exemplary/app-audio.json new file mode 100644 index 00000000000..589842c8876 --- /dev/null +++ b/adapters/consumable/consumable/exemplary/app-audio.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": [ + "audio/mp3" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": [ + "audio/mp3" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 3 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "mtype": 3 + }, + "type": "audio" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/consumable/consumable/exemplary/app-banner.json b/adapters/consumable/consumable/exemplary/app-banner.json new file mode 100644 index 00000000000..688ccb74c05 --- /dev/null +++ b/adapters/consumable/consumable/exemplary/app-banner.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/consumable/consumable/exemplary/app-video.json b/adapters/consumable/consumable/exemplary/app-video.json new file mode 100644 index 00000000000..a3772933aba --- /dev/null +++ b/adapters/consumable/consumable/exemplary/app-video.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "cat": ["IAB15"], + "dur": 30, + "mtype": 2 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "cat": ["IAB15"], + "dur": 30, + "mtype": 2 + }, + "type": "video", + "BidType": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/consumable/consumable/exemplary/simple-banner.json b/adapters/consumable/consumable/exemplary/simple-banner.json index 173104ee9ad..2e401b88087 100644 --- a/adapters/consumable/consumable/exemplary/simple-banner.json +++ b/adapters/consumable/consumable/exemplary/simple-banner.json @@ -5,20 +5,32 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { "networkId": 11, "siteId": 32, - "unitId": 42 + "unitId": 42, + "unitName": "cnsmbl-audio-728x90-slider" } } } ], "device": { "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", - "ip": "123.123.123.123" + "ip": "123.123.123.123", + "geo": { + "country": "USA", + "region": "IL", + "city": "Elgin", + "zip": "60123" + } }, "site": { "domain": "www.some.com", @@ -28,75 +40,68 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "geo": { + "country": "USA", + "region": "IL", + "city": "Elgin", + "zip": "60123" + } + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42, + "unitName": "cnsmbl-audio-728x90-slider" + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" - }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -107,14 +112,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/app-audio-bad-params.json b/adapters/consumable/consumable/supplemental/app-audio-bad-params.json new file mode 100644 index 00000000000..2dadc39c111 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/app-audio-bad-params.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "PlacementId is required for non-site requests", + "comparison": "literal" + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/app-banner-no-ad.json b/adapters/consumable/consumable/supplemental/app-banner-no-ad.json new file mode 100644 index 00000000000..c3d7238e104 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/app-banner-no-ad.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + "expectedBidResponses": [ + + ] +} \ No newline at end of file diff --git a/adapters/consumable/consumable/supplemental/app-video-no-media-type.json b/adapters/consumable/consumable/supplemental/app-video-no-media-type.json new file mode 100644 index 00000000000..3cba39bb5b5 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/app-video-no-media-type.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "cat": ["IAB15"], + "dur": 30 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + + ] + } + ] +} \ No newline at end of file diff --git a/adapters/consumable/consumable/supplemental/bad-dsp-request-example.json b/adapters/consumable/consumable/supplemental/bad-dsp-request-example.json new file mode 100644 index 00000000000..c66bb9ab5de --- /dev/null +++ b/adapters/consumable/consumable/supplemental/bad-dsp-request-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/dsp-server-internal-error-example.json b/adapters/consumable/consumable/supplemental/dsp-server-internal-error-example.json new file mode 100644 index 00000000000..07ad8e2358a --- /dev/null +++ b/adapters/consumable/consumable/supplemental/dsp-server-internal-error-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json index 56d24166fec..220c8ab2069 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{ "w": 728, "h": 250 }] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -36,71 +41,69 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": ["application/json"], - "Content-Type": ["application/json"], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": ["123.123.123.123"], - "Forwarded": ["for=123.123.123.123"], - "Origin": ["http://www.some.com"], - "Referer": ["http://www.some.com/page-where-ad-will-be-shown"] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "content": { - "id": "1009680902", - "title": "What You Know Bout Love", - "context": 3, - "url": "https://www.deezer.com/track/1009680902", - "artist": "Pop Smoke", - "album": "Shoot For The Stars Aim For The Moon" + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" }, - "placements": [ + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" - }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user": {}, - "enableBotFiltering": true, - "parallel": true + "site": { + "content": { + "album": "Shoot For The Stars Aim For The Moon", + "artist": "Pop Smoke", + "context": 3, + "id": "1009680902", + "title": "What You Know Bout Love", + "url": "https://www.deezer.com/track/1009680902" + }, + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents": [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -111,14 +114,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-coppa.json b/adapters/consumable/consumable/supplemental/simple-banner-coppa.json index d1551c92e7c..25b732a21ce 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-coppa.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-coppa.json @@ -31,76 +31,64 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "regs": { + "coppa": 1 }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "coppa": true + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -111,14 +99,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-eids.json b/adapters/consumable/consumable/supplemental/simple-banner-eids.json index 106796e21c0..d273117b1ae 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-eids.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-eids.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -26,103 +31,99 @@ }, "user": { "ext": { - "eids": [{ - "source": "adserver.org", - "uids": [{ - "id": "TTD_ID", - "atype": 1, - "ext": { - "rtiPartner": "TDID" - } - }] - }] + "eids": [ + { + "source": "adserver.org", + "uids": [ + { + "id": "TTD_ID", + "atype": 1, + "ext": { + "rtiPartner": "TDID" + } + } + ] + } + ] } } }, "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, "user": { - "eids": [{ - "source": "adserver.org", - "uids": [{ - "id": "TTD_ID", - "atype": 1, - "ext": { - "rtiPartner": "TDID" + "ext": { + "eids": [ + { + "source": "adserver.org", + "uids": [ + { + "atype": 1, + "ext": { + "rtiPartner": "TDID" + }, + "id": "TTD_ID" + } + ] } - }] - }] - }, - "enableBotFiltering": true, - "gdpr": {}, - "parallel": true + ] + } + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -133,14 +134,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json index 2e2d8588326..b2dbc9bdfbf 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-2.json @@ -33,78 +33,66 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "regs": { + "ext": { + "gdpr": 0 + } }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "gdpr": { - "applies": false + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -115,14 +103,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json index ea7be2342b2..fccc96ed6df 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr-3.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -33,78 +38,66 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "gdpr": { - "consent": "abcdefghijklm" + "user": { + "ext": { + "consent": "abcdefghijklm" + } } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -115,14 +108,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json b/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json index 3f6a953efa8..99105a77325 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gdpr.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -38,79 +43,71 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "regs": { + "ext": { + "gdpr": 1 + } + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "gdpr": { - "applies": true, - "consent": "abcdefghijklm" + "user": { + "ext": { + "consent": "abcdefghijklm" + } } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -121,14 +118,15 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 + }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gpp.json b/adapters/consumable/consumable/supplemental/simple-banner-gpp.json index 9b2e3d75031..eae291c1c61 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-gpp.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-gpp.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -26,83 +31,75 @@ }, "regs": { "gpp": "gppString", - "gpp_sid": [7] + "gpp_sid": [ + 7 + ] } }, "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "regs": { + "gpp": "gppString", + "gpp_sid": [ + 7 + ] }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "gpp": "gppString", - "gpp_sid": [7] + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -113,14 +110,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-meta.json b/adapters/consumable/consumable/supplemental/simple-banner-meta.json index 6c94c835aab..782ddc7bbe7 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-meta.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-meta.json @@ -28,77 +28,61 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" - }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } - ], - "adomain": ["consumabletv.com"], - "cats": ["IAB1", "IAB2"] + ] } - } + ], + "cur": "USD" } } } @@ -109,16 +93,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250, - "adomain": ["consumabletv.com"], - "cat": ["IAB1", "IAB2"] + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json b/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json index 6e9787ee163..fddf160a548 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-no-impressionUrl.json @@ -5,7 +5,12 @@ { "id": "test-no-impUrl-id", "banner": { - "format": [{"w": 300, "h": 250}] + "format": [ + { + "w": 300, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -30,104 +35,87 @@ "buyeruid": "azk-user-id" } }, - "httpCalls": [ { "expectedRequest": { - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "Cookie": [ - "azk=azk-user-id" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, - "uri": "https://e.serverbid.com/api/v2", + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [5], - "divName": "test-no-impUrl-id", - "networkId": 11, - "siteId": 32, - "unitId": 42, - "unitName": "the-answer" + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42, + "unitName": "the-answer" + } + }, + "id": "test-no-impUrl-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown", + "ref": "http://www.some.com/page-before-the-ad-if-any" }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "unitName": "the-answer", - "time": 1451651415, - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "referrer": "http://www.some.com/page-before-the-ad-if-any" + "user": { + "buyeruid": "azk-user-id" + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-no-impUrl-id": { - "adId": 1234567890, - "bidderName": "aol", - "pricing": { - "clearPrice": 0.5 - }, - "width": 300, - "height": 250, - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } ], - "expectedBidResponses": [ { "currency": "USD", "bids": [ { "bid": { - "id": "test-request-id", - "crid": "1234567890", - "impid": "test-no-impUrl-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "exp": 30, - "w": 300, - "h": 250 + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-no-params.json b/adapters/consumable/consumable/supplemental/simple-banner-no-params.json new file mode 100644 index 00000000000..03d1ca9ce3e --- /dev/null +++ b/adapters/consumable/consumable/supplemental/simple-banner-no-params.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "123.123.123.123", + "geo": { + "country": "USA", + "region": "IL", + "city": "Elgin", + "zip": "60123" + } + }, + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "SiteId, NetworkId and UnitId are all required for site requests", + "comparison": "literal" + } + ] +} diff --git a/adapters/consumable/consumable/supplemental/simple-banner-schain.json b/adapters/consumable/consumable/supplemental/simple-banner-schain.json index 0b963a507e7..c3491e6d1e0 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-schain.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-schain.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -43,81 +48,76 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "schain": { - "complete": 1, - "nodes": [ - { - "asi": "indirectseller.com", - "hp": 1, - "sid": "00001" + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "indirectseller.com", + "hp": 1, + "sid": "00001" + } + ], + "ver": "1.0" } - ], - "ver": "1.0" + } } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -128,14 +128,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json b/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json index c09200c4bd3..bf27672805c 100644 --- a/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json +++ b/adapters/consumable/consumable/supplemental/simple-banner-us-privacy.json @@ -5,7 +5,12 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 250}] + "format": [ + { + "w": 728, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -33,76 +38,66 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://e.serverbid.com/api/v2", - "headers": { - "Accept": [ - "application/json" - ], - "Content-Type": [ - "application/json" - ], - "User-Agent": [ - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ], - "Forwarded": [ - "for=123.123.123.123" - ], - "Origin": [ - "http://www.some.com" - ], - "Referer": [ - "http://www.some.com/page-where-ad-will-be-shown" - ] - }, + "uri": "https://e.serverbid.com/sb/rtb", "body": { - "placements": [ + "device": { + "ip": "123.123.123.123", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36" + }, + "id": "test-request-id", + "imp": [ { - "adTypes": [2730], - "divName": "test-imp-id", - "networkId": 11, - "siteId": 32, - "unitId": 42 + "banner": { + "format": [ + { + "h": 250, + "w": 728 + } + ] + }, + "ext": { + "bidder": { + "networkId": 11, + "siteId": 32, + "unitId": 42 + } + }, + "id": "test-imp-id" } ], - "schain": { - "complete": 0, - "nodes": null, - "ver": "" + "site": { + "domain": "www.some.com", + "page": "http://www.some.com/page-where-ad-will-be-shown" }, - "networkId": 11, - "siteId": 32, - "unitId": 42, - "time": 1451651415, - "url": "http://www.some.com/page-where-ad-will-be-shown", - "includePricingData": true, - "user":{}, - "enableBotFiltering": true, - "parallel": true, - "ccpa": "1NYN" + "regs": { + "ext": { + "us_privacy": "1NYN" + } + } } }, "mockResponse": { "status": 200, "body": { - "decisions": { - "test-imp-id": { - "adId": 1234567890, - "pricing": { - "clearPrice": 0.5 - }, - "width": 728, - "height": 250, - "impressionUrl": "http://localhost:8080/shown", - "contents" : [ + "id": "test-request-id", + "seatbid": [ + { + "seat": "bmtm", + "bid": [ { - "body": "Remember this: https://www.google.com/search?q=blink+tag ?" + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "mtype": 1 } ] } - } + ], + "cur": "USD" } } } @@ -113,14 +108,14 @@ "bids": [ { "bid": { - "id": "test-request-id", + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", "price": 0.5, - "adm": "Remember this: https://www.google.com/search?q=blink+tag ?", - "crid": "1234567890", - "exp": 30, + "adm": "some-test-ad", + "crid": "crid_10", "w": 728, - "h": 250 + "h": 90, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/consumable/consumable/supplemental/unknown-status-code-example.json b/adapters/consumable/consumable/supplemental/unknown-status-code-example.json new file mode 100644 index 00000000000..58fafcff6b2 --- /dev/null +++ b/adapters/consumable/consumable/supplemental/unknown-status-code-example.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://e.serverbid.com/rtb/bid?s=0421008445828ceb46f496700a5fa65e", + "body": { + "id": "test-request-id", + "app": { + "id": "1", + "bundle": "com.foo.bar" + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "ip": "73.55.27.72" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "placementId": "0421008445828ceb46f496700a5fa65e" + } + } + } + ] + } + }, + "mockResponse": { + "status": 301, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 301. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/consumable/consumable_test.go b/adapters/consumable/consumable_test.go index 5cfe3fe2824..75e69535f20 100644 --- a/adapters/consumable/consumable_test.go +++ b/adapters/consumable/consumable_test.go @@ -1,14 +1,14 @@ package consumable import ( + "encoding/json" + "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "testing" - "time" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -19,18 +19,75 @@ func TestJsonSamples(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - assertClock(t, bidder) - replaceClockWithKnownTime(bidder) - adapterstest.RunJSONBidderTest(t, "consumable", bidder) } -func assertClock(t *testing.T, bidder adapters.Bidder) { - bidderConsumable, _ := bidder.(*ConsumableAdapter) - assert.NotNil(t, bidderConsumable.clock) -} +func TestConsumableMakeBidsWithCategoryDuration(t *testing.T) { + bidder := &adapter{} + + mockedReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "1_1", + Video: &openrtb2.Video{ + W: 640, + H: 360, + MIMEs: []string{"video/mp4"}, + MaxDuration: 60, + Protocols: []adcom1.MediaCreativeSubtype{2, 3, 5, 6}, + }, + Ext: json.RawMessage( + `{ + "prebid": {}, + "bidder": { + "placementId": "123456" + } + }`, + )}, + }, + } + mockedExtReq := &adapters.RequestData{} + mockedBidResponse := &openrtb2.BidResponse{ + ID: "test-1", + SeatBid: []openrtb2.SeatBid{{ + Seat: "Buyer", + Bid: []openrtb2.Bid{{ + ID: "1", + ImpID: "1_1", + Price: 1.23, + AdID: "123", + Cat: []string{"IAB18-1"}, + Dur: 30, + MType: openrtb2.MarkupVideo, + }}, + }}, + } + body, _ := json.Marshal(mockedBidResponse) + mockedRes := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } -func replaceClockWithKnownTime(bidder adapters.Bidder) { - bidderConsumable, _ := bidder.(*ConsumableAdapter) - bidderConsumable.clock = knownInstant(time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC)) + expectedBidCount := 1 + expectedBidType := openrtb_ext.BidTypeVideo + expectedBidDuration := 30 + expectedBidCategory := "IAB18-1" + expectedErrorCount := 0 + + bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes) + + if len(bidResponse.Bids) != expectedBidCount { + t.Errorf("should have 1 bid, bids=%v", bidResponse.Bids) + } + if bidResponse.Bids[0].BidType != expectedBidType { + t.Errorf("bid type should be video, bidType=%s", bidResponse.Bids[0].BidType) + } + if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration { + t.Errorf("video duration should be set") + } + if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory { + t.Errorf("bid category should be set") + } + if len(errors) != expectedErrorCount { + t.Errorf("should not have any errors, errors=%v", errors) + } } diff --git a/adapters/consumable/instant.go b/adapters/consumable/instant.go deleted file mode 100644 index a6162d44e22..00000000000 --- a/adapters/consumable/instant.go +++ /dev/null @@ -1,21 +0,0 @@ -package consumable - -import "time" - -type instant interface { - Now() time.Time -} - -// Send a real instance when you construct it in adapter_map.go -type realInstant struct{} - -func (realInstant) Now() time.Time { - return time.Now() -} - -// Use this for tests e.g. knownInstant(time.Date(y, m, ..., time.UTC)) -type knownInstant time.Time - -func (i knownInstant) Now() time.Time { - return time.Time(i) -} diff --git a/adapters/consumable/params_test.go b/adapters/consumable/params_test.go index 570b4128339..99a993e9752 100644 --- a/adapters/consumable/params_test.go +++ b/adapters/consumable/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/consumable.json @@ -43,6 +43,7 @@ var validParams = []string{ `{"networkId": 22, "siteId": 1, "unitId": 101, "unitName": "unit-1"}`, `{"networkId": 22, "siteId": 1, "unitId": 101, "unitName": "-unit-1"}`, // unitName can start with a dash `{"networkId": 22, "siteId": 1, "unitId": 101}`, // unitName can be omitted (although prebid.js doesn't allow that) + `{"placementId": "abcdjk232"}`, } var invalidParams = []string{ @@ -56,4 +57,5 @@ var invalidParams = []string{ `{"siteId": 1, "unitId": 101, "unitName": 11}`, // networkId must be present `{"networkId": 22, "unitId": 101, "unitName": 11}`, // siteId must be present `{"siteId": 1, "networkId": 22, "unitName": 11}`, // unitId must be present + `{"placementId": "---abjk;jkewj;k;jwejklfs}`, // placementId must be alphanumeric } diff --git a/adapters/consumable/retrieveAd.go b/adapters/consumable/retrieveAd.go deleted file mode 100644 index 7f69a1bbc23..00000000000 --- a/adapters/consumable/retrieveAd.go +++ /dev/null @@ -1,10 +0,0 @@ -package consumable - -func retrieveAd(decision decision) string { - - if decision.Contents != nil && len(decision.Contents) > 0 { - return decision.Contents[0].Body - } - - return "" -} diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index ec4d2078df8..56ed4b0865c 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ConversantAdapter struct { diff --git a/adapters/conversant/conversant_test.go b/adapters/conversant/conversant_test.go index ac3f5a2b633..1c08ea557ed 100644 --- a/adapters/conversant/conversant_test.go +++ b/adapters/conversant/conversant_test.go @@ -3,9 +3,9 @@ package conversant import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cpmstar/cpmstar.go b/adapters/cpmstar/cpmstar.go index ecb963311b9..f337c1477c5 100644 --- a/adapters/cpmstar/cpmstar.go +++ b/adapters/cpmstar/cpmstar.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type Adapter struct { diff --git a/adapters/cpmstar/cpmstar_test.go b/adapters/cpmstar/cpmstar_test.go index 9218ef4ce2c..2a08258358b 100644 --- a/adapters/cpmstar/cpmstar_test.go +++ b/adapters/cpmstar/cpmstar_test.go @@ -3,9 +3,9 @@ package cpmstar import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cpmstar/params_test.go b/adapters/cpmstar/params_test.go index 45b1fbefd96..e0dcb9ced52 100644 --- a/adapters/cpmstar/params_test.go +++ b/adapters/cpmstar/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/cpmstar.json diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index 11d8d166ba6..cef79d46756 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go index 28654acd310..ae538246444 100644 --- a/adapters/criteo/criteo_test.go +++ b/adapters/criteo/criteo_test.go @@ -1,10 +1,11 @@ package criteo import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index c373de0a83f..aa17aa87e2e 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/criteo.json diff --git a/adapters/cwire/cwire.go b/adapters/cwire/cwire.go index 97a51ed764b..0c51fed908d 100644 --- a/adapters/cwire/cwire.go +++ b/adapters/cwire/cwire.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) /* diff --git a/adapters/cwire/cwire_test.go b/adapters/cwire/cwire_test.go index b51b4e1b007..bd5f32e99ff 100644 --- a/adapters/cwire/cwire_test.go +++ b/adapters/cwire/cwire_test.go @@ -3,9 +3,9 @@ package cwire import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/cwire/params_test.go b/adapters/cwire/params_test.go index 99689977530..d8c8b117069 100644 --- a/adapters/cwire/params_test.go +++ b/adapters/cwire/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/cwire.json diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index 8929c84853a..22bf67bdbbe 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DatablocksAdapter struct { @@ -27,7 +27,7 @@ func (a *DatablocksAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * "Accept": {"application/json"}, } - // Pull the host and source ID info from the bidder params. + // Pull the source ID info from the bidder params. reqImps, err := splitImpressions(request.Imp) if err != nil { @@ -45,7 +45,7 @@ func (a *DatablocksAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo * continue } - urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)} + urlParams := macros.EndpointTemplateParams{SourceId: strconv.Itoa(reqExt.SourceId)} url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams) if err != nil { @@ -137,7 +137,7 @@ func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpDatablocks, error) { var datablocksExt openrtb_ext.ExtImpDatablocks if err := json.Unmarshal(bidderExt.Bidder, &datablocksExt); err != nil { return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()), + Message: fmt.Sprintf("Cannot Resolve sourceId: %s", err.Error()), } } @@ -147,12 +147,6 @@ func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpDatablocks, error) { } } - if len(datablocksExt.Host) < 1 { - return nil, &errortypes.BadInput{ - Message: "Invalid/Missing Host", - } - } - return &datablocksExt, nil } diff --git a/adapters/datablocks/datablocks_test.go b/adapters/datablocks/datablocks_test.go index 9a5fb9a23f4..d97560ce332 100644 --- a/adapters/datablocks/datablocks_test.go +++ b/adapters/datablocks/datablocks_test.go @@ -3,15 +3,15 @@ package datablocks import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderDatablocks, config.Adapter{ - Endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + Endpoint: "http://pbserver.dblks.net/openrtb2?sid={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/datablocks/datablockstest/exemplary/multi-request.json b/adapters/datablocks/datablockstest/exemplary/multi-request.json index 286e058a72e..eafd80927fa 100644 --- a/adapters/datablocks/datablockstest/exemplary/multi-request.json +++ b/adapters/datablocks/datablockstest/exemplary/multi-request.json @@ -17,7 +17,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -34,7 +34,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -54,7 +54,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "uri": "http://pbserver.dblks.net/openrtb2?sid=906295", "body": { "id": "some-request-id", @@ -73,7 +73,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -91,7 +91,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } diff --git a/adapters/datablocks/datablockstest/exemplary/native.json b/adapters/datablocks/datablockstest/exemplary/native.json index 9487079c734..c4cf189e1b4 100644 --- a/adapters/datablocks/datablockstest/exemplary/native.json +++ b/adapters/datablocks/datablockstest/exemplary/native.json @@ -14,7 +14,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -38,7 +38,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "uri": "http://pbserver.dblks.net/openrtb2?sid=906295", "body": { "id": "some-request-id", @@ -54,7 +54,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } diff --git a/adapters/datablocks/datablockstest/exemplary/simple-banner.json b/adapters/datablocks/datablockstest/exemplary/simple-banner.json index 289549ab588..5c01a29a519 100644 --- a/adapters/datablocks/datablockstest/exemplary/simple-banner.json +++ b/adapters/datablocks/datablockstest/exemplary/simple-banner.json @@ -21,7 +21,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -41,7 +41,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "uri": "http://pbserver.dblks.net/openrtb2?sid=906295", "body": { "id": "some-request-id", @@ -64,7 +64,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } diff --git a/adapters/datablocks/datablockstest/exemplary/simple-video.json b/adapters/datablocks/datablockstest/exemplary/simple-video.json index 6f8073107f3..fcb2defa649 100644 --- a/adapters/datablocks/datablockstest/exemplary/simple-video.json +++ b/adapters/datablocks/datablockstest/exemplary/simple-video.json @@ -18,7 +18,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } @@ -39,7 +39,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", + "uri": "http://pbserver.dblks.net/openrtb2?sid=906295", "body": { "id": "some-request-id", @@ -59,7 +59,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 906295 } } diff --git a/adapters/datablocks/datablockstest/supplemental/bad-host.json b/adapters/datablocks/datablockstest/supplemental/bad-host.json deleted file mode 100644 index cee5efbe760..00000000000 --- a/adapters/datablocks/datablockstest/supplemental/bad-host.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "bad-host-test", - "imp": [ - { - "id": "bad-host-test-imp", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": - { - "bidder": - { - "host": "", - "sourceId": 123 - } - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Invalid/Missing Host", - "comparison": "literal" - } - ] -} diff --git a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json index 7c4801e1685..e60c6aabb7d 100644 --- a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json +++ b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json @@ -17,7 +17,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } @@ -37,7 +37,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "uri": "http://pbserver.dblks.net/openrtb2?sid=123", "body": { "id": "some-request-id", @@ -56,7 +56,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } diff --git a/adapters/datablocks/datablockstest/supplemental/bad-server-response.json b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json index da2924f6f21..cdcc0c37d4c 100644 --- a/adapters/datablocks/datablockstest/supplemental/bad-server-response.json +++ b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json @@ -17,7 +17,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } @@ -37,7 +37,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "uri": "http://pbserver.dblks.net/openrtb2?sid=123", "body": { "id": "some-request-id", @@ -56,7 +56,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } diff --git a/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json index 08f2fddf568..3182e71dc84 100644 --- a/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json +++ b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json @@ -16,7 +16,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 0 } } diff --git a/adapters/datablocks/datablockstest/supplemental/missing-extparam.json b/adapters/datablocks/datablockstest/supplemental/missing-extparam.json deleted file mode 100644 index d272cd5347c..00000000000 --- a/adapters/datablocks/datablockstest/supplemental/missing-extparam.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "mockBidRequest": { - "id": "missing-extbid-test", - "imp": [ - { - "id": "missing-extbid-test", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "sourceId":54326 - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Cannot Resolve host or sourceId: unexpected end of JSON input", - "comparison": "literal" - } - ] - - -} diff --git a/adapters/datablocks/datablockstest/supplemental/no-content-response.json b/adapters/datablocks/datablockstest/supplemental/no-content-response.json index 7fc1b75c3ad..0e53f4720a9 100644 --- a/adapters/datablocks/datablockstest/supplemental/no-content-response.json +++ b/adapters/datablocks/datablockstest/supplemental/no-content-response.json @@ -17,7 +17,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } @@ -37,7 +37,7 @@ { "expectedRequest": { - "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123", + "uri": "http://pbserver.dblks.net/openrtb2?sid=123", "body": { "id": "some-request-id", @@ -56,7 +56,7 @@ { "bidder": { - "host": "search.nutella.datablocks.net", + "host": "pbserver.dblks.net", "sourceId": 123 } } diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go index d959c9b41ca..bf673ee1691 100644 --- a/adapters/decenterads/decenterads.go +++ b/adapters/decenterads/decenterads.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/decenterads/decenterads_test.go b/adapters/decenterads/decenterads_test.go index 3c1ed971833..dfb5161b9c4 100644 --- a/adapters/decenterads/decenterads_test.go +++ b/adapters/decenterads/decenterads_test.go @@ -3,9 +3,9 @@ package decenterads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/decenterads/params_test.go b/adapters/decenterads/params_test.go index 3d3708be789..ef8b47cce41 100644 --- a/adapters/decenterads/params_test.go +++ b/adapters/decenterads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the decenterads schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 765c43e5c13..1bda503ae32 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const displayManager string = "di_prebid" diff --git a/adapters/deepintent/deepintent_test.go b/adapters/deepintent/deepintent_test.go index 97f685d2f7e..9b2c92967b4 100644 --- a/adapters/deepintent/deepintent_test.go +++ b/adapters/deepintent/deepintent_test.go @@ -3,10 +3,10 @@ package deepintent import ( "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/deepintent/params_test.go b/adapters/deepintent/params_test.go index 8f37e5a9bd6..4cd43b73ebe 100644 --- a/adapters/deepintent/params_test.go +++ b/adapters/deepintent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the deepintent schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/definemedia/definemedia.go b/adapters/definemedia/definemedia.go index 3e014e3c16d..789902d0485 100644 --- a/adapters/definemedia/definemedia.go +++ b/adapters/definemedia/definemedia.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/definemedia/definemedia_test.go b/adapters/definemedia/definemedia_test.go index 3ed0cb938b8..8a2a860d0c0 100644 --- a/adapters/definemedia/definemedia_test.go +++ b/adapters/definemedia/definemedia_test.go @@ -3,9 +3,9 @@ package definemedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/definemedia/params_test.go b/adapters/definemedia/params_test.go index 63ef5272669..45b5be4eae5 100644 --- a/adapters/definemedia/params_test.go +++ b/adapters/definemedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/dianomi/dianomi.go b/adapters/dianomi/dianomi.go index 10605ef24a7..f7b97748c5b 100644 --- a/adapters/dianomi/dianomi.go +++ b/adapters/dianomi/dianomi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/dianomi/dianomi_test.go b/adapters/dianomi/dianomi_test.go index 95c94a02f14..1baa4b591b3 100644 --- a/adapters/dianomi/dianomi_test.go +++ b/adapters/dianomi/dianomi_test.go @@ -3,9 +3,9 @@ package dianomi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/dianomi/params_test.go b/adapters/dianomi/params_test.go index 462d6d75edd..43a9bf6f4f5 100644 --- a/adapters/dianomi/params_test.go +++ b/adapters/dianomi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/dianomi.json diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go index 4f1f23a625c..488c3d46453 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -10,10 +10,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DmxAdapter struct { diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 1634e6b5956..c709d5b1617 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestFetchParams(t *testing.T) { diff --git a/adapters/dmx/params_test.go b/adapters/dmx/params_test.go index 0e5250b173e..4470fb23057 100644 --- a/adapters/dmx/params_test.go +++ b/adapters/dmx/params_test.go @@ -2,8 +2,9 @@ package dmx import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/dxkulture/dxkulture.go b/adapters/dxkulture/dxkulture.go new file mode 100644 index 00000000000..8155f59a203 --- /dev/null +++ b/adapters/dxkulture/dxkulture.go @@ -0,0 +1,170 @@ +package dxkulture + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +var markupTypeToBidType = map[openrtb2.MarkupType]openrtb_ext.BidType{ + openrtb2.MarkupBanner: openrtb_ext.BidTypeBanner, + openrtb2.MarkupVideo: openrtb_ext.BidTypeVideo, +} + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the DXKulture adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impressions := request.Imp + + adapterRequests := make([]*adapters.RequestData, 0, len(impressions)) + var errs []error + + for _, impression := range impressions { + impExt, err := parseExt(&impression) + if err != nil { + errs = append(errs, err) + continue + } + + request.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + + if request.Test == 1 { + impExt.PublisherId = "test" + } + + params := url.Values{} + params.Add("publisher_id", impExt.PublisherId) + params.Add("placement_id", impExt.PlacementId) + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint + "?" + params.Encode(), + Body: body, + Headers: getHeaders(request), + }) + } + + request.Imp = impressions + return adapterRequests, errs +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var ortbResponse openrtb2.BidResponse + err := json.Unmarshal(response.Body, &ortbResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + var bidErrors []error + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(1) + for _, seatBid := range ortbResponse.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getBidType(&bid) + if err != nil { + bidErrors = append(bidErrors, err) + continue + } + + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + + return bidderResponse, bidErrors +} + +func getBidType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + if bidType, ok := markupTypeToBidType[bid.MType]; ok { + return bidType, nil + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d", bid.MType), + } +} + +func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpDXKulture, error) { + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpDXKulture{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + return &impExt, nil +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Site != nil { + if request.Site.Ref != "" { + headers.Set("Referer", request.Site.Ref) + } + if request.Site.Domain != "" { + headers.Add("Origin", request.Site.Domain) + } + } + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + return headers +} diff --git a/adapters/dxkulture/dxkulture_test.go b/adapters/dxkulture/dxkulture_test.go new file mode 100644 index 00000000000..7344d5a9d51 --- /dev/null +++ b/adapters/dxkulture/dxkulture_test.go @@ -0,0 +1,17 @@ +package dxkulture + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/config" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder("dxkulture", config.Adapter{Endpoint: "https://ads.dxkulture.com/pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "dxkulturetest", bidder) +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/banner.json b/adapters/dxkulture/dxkulturetest/exemplary/banner.json new file mode 100644 index 00000000000..86b0e928c67 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/banner.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 1 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json b/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json new file mode 100644 index 00000000000..7460153f5ab --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/empty-site-domain-ref.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json b/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json new file mode 100644 index 00000000000..20841500d58 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/ipv6.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ipv6": "2001:0000:130F:0000:0000:09C0:876A:130B" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "2001:0000:130F:0000:0000:09C0:876A:130B" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ipv6": "2001:0000:130F:0000:0000:09C0:876A:130B" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "page": "http://site.com/page" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json b/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json new file mode 100644 index 00000000000..f1d0a840ba0 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/video-test-request.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=test", + "body": { + "id": "test-request-id", + "test": 1, + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/exemplary/video.json b/adapters/dxkulture/dxkulturetest/exemplary/video.json new file mode 100644 index 00000000000..37f4b9e8236 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/exemplary/video.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json new file mode 100644 index 00000000000..ae30b327030 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext-bidder.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": "not_json" + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpDXKulture", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..2587dc216d2 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-imp-ext.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": "not_json" + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json b/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json new file mode 100644 index 00000000000..4ef0539c69b --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/invalid-response.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": "body" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json b/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json new file mode 100644 index 00000000000..a65a46ef0ee --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/no-mtype.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "43271b2d-41c0-4093-8ba1-2105d9658e80", + "crid": "16329", + "adomain": [ + "adomain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "2422", + "adm": "" + } + ], + "seat": "dxkulture" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 0", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] + +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..0338be9f811 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-bad-request.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..4363b998cf7 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-no-content.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json b/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..608521b42a0 --- /dev/null +++ b/adapters/dxkulture/dxkulturetest/supplemental/status-code-other-error.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://site.com/ref" + ], + "Origin": [ + "site.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "user-agent" + ], + "X-Forwarded-For": [ + "1.2.3.4" + ], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "https://ads.dxkulture.com/pbs?placement_id=placement123&publisher_id=pub123", + "body": { + "id": "test-request-id", + "user": { + "buyeruid": "userId", + "yob": 1990 + }, + "device": { + "ua": "user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 1920, + "h": 1080, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "pub123", + "placementId": "placement123" + } + } + } + ], + "site": { + "domain": "site.com", + "page": "http://site.com/page", + "ref": "http://site.com/ref" + } + } + }, + "mockResponse": { + "status": 505 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 505. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/dxkulture/params_test.go b/adapters/dxkulture/params_test.go new file mode 100644 index 00000000000..d1c2f91a7ce --- /dev/null +++ b/adapters/dxkulture/params_test.go @@ -0,0 +1,53 @@ +package dxkulture + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/dxkulture.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.dxkulture + +// TestValidParams makes sure that the dxkulture schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderDXKulture, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected dxkulture params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the dxkulture schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderDXKulture, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisherId": "pub", "placementId": "plac"}`, + `{"publisherId": "pub", "placementId": "plac", "a":1}`, +} + +var invalidParams = []string{ + `{"publisherId": "pub"}`, + `{"placementId": "plac"}`, + //malformed + `{"ub", "placementId": "plac"}`, + `{}`, +} diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go index af2c680f99c..4beb488f801 100644 --- a/adapters/e_volution/evolution.go +++ b/adapters/e_volution/evolution.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go index 9752a4c7587..4529a742077 100644 --- a/adapters/e_volution/evolution_test.go +++ b/adapters/e_volution/evolution_test.go @@ -3,9 +3,9 @@ package evolution import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go index 2d3602fd72b..6049bce7780 100644 --- a/adapters/e_volution/params_test.go +++ b/adapters/e_volution/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/edge226/edge226.go b/adapters/edge226/edge226.go new file mode 100644 index 00000000000..2196006b766 --- /dev/null +++ b/adapters/edge226/edge226.go @@ -0,0 +1,145 @@ +package edge226 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +type reqBodyExt struct { + Edge226BidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var err error + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb2.Imp{imp} + + var bidderExt adapters.ExtImpBidder + var edge226Ext openrtb_ext.ImpExtEdge226 + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &edge226Ext); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{Edge226BidderExt: reqBodyExtBidder{}} + + if edge226Ext.PlacementID != "" { + temp.Edge226BidderExt.PlacementID = edge226Ext.PlacementID + temp.Edge226BidderExt.Type = "publisher" + } else if edge226Ext.EndpointID != "" { + temp.Edge226BidderExt.EndpointID = edge226Ext.EndpointID + temp.Edge226BidderExt.Type = "network" + } + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, err +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getBidMediaType(&seatBid.Bid[i]) + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) + } +} diff --git a/adapters/edge226/edge226_test.go b/adapters/edge226/edge226_test.go new file mode 100644 index 00000000000..fea4abff1a9 --- /dev/null +++ b/adapters/edge226/edge226_test.go @@ -0,0 +1,20 @@ +package edge226 + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderEdge226, config.Adapter{ + Endpoint: "http://test.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "edge226test", bidder) +} diff --git a/adapters/edge226/edge226test/exemplary/endpointId.json b/adapters/edge226/edge226test/exemplary/endpointId.json new file mode 100644 index 00000000000..741c1f8cb9b --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/endpointId.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-banner.json b/adapters/edge226/edge226test/exemplary/simple-banner.json new file mode 100644 index 00000000000..741c1f8cb9b --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-native.json b/adapters/edge226/edge226test/exemplary/simple-native.json new file mode 100644 index 00000000000..581864cdb41 --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-native.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-video.json b/adapters/edge226/edge226test/exemplary/simple-video.json new file mode 100644 index 00000000000..c14a1bd988e --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-video.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/exemplary/simple-web-banner.json b/adapters/edge226/edge226test/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..6cc7295003c --- /dev/null +++ b/adapters/edge226/edge226test/exemplary/simple-web-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/edge226/edge226test/supplemental/bad_media_type.json b/adapters/edge226/edge226test/supplemental/bad_media_type.json new file mode 100644 index 00000000000..75b13412ad6 --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/bad_media_type.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": {} + } + ], + "seat": "edge226" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unable to fetch mediaType in multi-format: test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/edge226test/supplemental/bad_response.json b/adapters/edge226/edge226test/supplemental/bad_response.json new file mode 100644 index 00000000000..1e5664bafec --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/edge226test/supplemental/status-204.json b/adapters/edge226/edge226test/supplemental/status-204.json new file mode 100644 index 00000000000..da929ce61cb --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [] +} diff --git a/adapters/edge226/edge226test/supplemental/status-not-200.json b/adapters/edge226/edge226test/supplemental/status-not-200.json new file mode 100644 index 00000000000..cf1847c2cac --- /dev/null +++ b/adapters/edge226/edge226test/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/edge226/params_test.go b/adapters/edge226/params_test.go new file mode 100644 index 00000000000..21a83bd65fd --- /dev/null +++ b/adapters/edge226/params_test.go @@ -0,0 +1,47 @@ +package edge226 + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderEdge226, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderEdge226, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, +} diff --git a/adapters/emtv/emtv.go b/adapters/emtv/emtv.go index 5dbd1e1bde7..0f0cae04f1f 100644 --- a/adapters/emtv/emtv.go +++ b/adapters/emtv/emtv.go @@ -7,10 +7,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/emtv/emtv_test.go b/adapters/emtv/emtv_test.go index b1287357681..238e463a0d5 100644 --- a/adapters/emtv/emtv_test.go +++ b/adapters/emtv/emtv_test.go @@ -3,9 +3,9 @@ package emtv import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/emtv/params_test.go b/adapters/emtv/params_test.go index 966dd7dd460..40769b97942 100644 --- a/adapters/emtv/params_test.go +++ b/adapters/emtv/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/emx_digital/emx_digital_test.go b/adapters/emx_digital/emx_digital_test.go deleted file mode 100644 index 6538ff70efa..00000000000 --- a/adapters/emx_digital/emx_digital_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package emx_digital - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderEmxDigital, config.Adapter{ - Endpoint: "https://hb.emxdgt.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - setTesting(bidder) - adapterstest.RunJSONBidderTest(t, "emx_digitaltest", bidder) -} - -func setTesting(bidder adapters.Bidder) { - bidderEmxDigital, _ := bidder.(*EmxDigitalAdapter) - bidderEmxDigital.testing = true -} diff --git a/adapters/engagebdr/engagebdr.go b/adapters/engagebdr/engagebdr.go deleted file mode 100644 index eb0160172a0..00000000000 --- a/adapters/engagebdr/engagebdr.go +++ /dev/null @@ -1,151 +0,0 @@ -package engagebdr - -import ( - "encoding/json" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "fmt" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" -) - -type EngageBDRAdapter struct { - URI string -} - -func (adapter *EngageBDRAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - - errors := make([]error, 0, len(request.Imp)) - - if request.Imp == nil || len(request.Imp) == 0 { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid BidRequest. No valid imp."), - }) - return nil, errors - } - - // EngageBDR uses different sspid parameters for banner and video. - sspidImps := make(map[string][]openrtb2.Imp) - for _, imp := range request.Imp { - - if imp.Audio != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, invalid MediaType. EngageBDR only supports Banner, Video and Native.", imp.ID), - }) - continue - } - - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s.", imp.ID, err), - }) - continue - } - impExt := openrtb_ext.ExtImpEngageBDR{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s.", imp.ID, err), - }) - continue - } - if impExt.Sspid == "" { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("Ignoring imp id=%s, no sspid present.", imp.ID), - }) - continue - } - sspidImps[impExt.Sspid] = append(sspidImps[impExt.Sspid], imp) - } - - var adapterRequests []*adapters.RequestData - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - - for sspid, imps := range sspidImps { - if len(imps) > 0 { - // Make a copy as we don't want to change the original request - reqCopy := *request - reqCopy.Imp = imps - reqJSON, err := json.Marshal(reqCopy) - if err != nil { - errors = append(errors, err) - return nil, errors - } - adapterReq := adapters.RequestData{ - Method: "POST", - Uri: adapter.URI + "?zoneid=" + sspid, - Body: reqJSON, - Headers: headers, - } - adapterRequests = append(adapterRequests, &adapterReq) - } - } - - return adapterRequests, errors -} - -func (adapter *EngageBDRAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), - }) - } - } - return bidResponse, nil -} - -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } else if imp.Native != nil { - mediaType = openrtb_ext.BidTypeNative - } - return mediaType - } - } - return mediaType -} - -// Builder builds a new instance of the EngageBDR adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &EngageBDRAdapter{ - URI: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/engagebdr/engagebdr_test.go b/adapters/engagebdr/engagebdr_test.go deleted file mode 100644 index 0877750cb19..00000000000 --- a/adapters/engagebdr/engagebdr_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package engagebdr - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderEngageBDR, config.Adapter{ - Endpoint: "http://dsp.bnmla.com/hb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "engagebdrtest", bidder) -} diff --git a/adapters/engagebdr/engagebdrtest/exemplary/banner.json b/adapters/engagebdr/engagebdrtest/exemplary/banner.json deleted file mode 100644 index 92b79e8f349..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/banner.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json b/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json deleted file mode 100644 index d11e38c46fc..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/multi-banner.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - { - "id" : "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.50, - "adid": "abcde-12345-2", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.50, - "adid": "abcde-12345-2", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json b/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json deleted file mode 100644 index 9506c963578..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/multi-video.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - }, - { - "id": "test-imp-id-2", - "video": { - "w": 320, - "mimes": null, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99998", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - }, - { - "id": "test-imp-id-2", - "video": { - "w": 320, - "mimes": null, - "h": 50 - }, - "ext": { - "bidder": { - "sspid": "99998" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.81, - "adid": "abcde-12345-2", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "video" - }, - { - "bid": { - "id": "test-imp-id-2", - "impid": "test-imp-id-2", - "price": 7.81, - "adid": "abcde-12345-2", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345-2", - "w": 320, - "h": 50 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/engagebdr/engagebdrtest/exemplary/native.json b/adapters/engagebdr/engagebdrtest/exemplary/native.json deleted file mode 100644 index 963194fb8bd..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/native.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver":"1.1" - }, - "ext": { - "bidder": { - "sspid": "99997" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99997", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "native": { - "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", - "ver":"1.1" - }, - "ext": { - "bidder": { - "sspid":"99997" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "{\"native\":{\"link\":{\"url\":\"https://rtb-use.mfadsrvr.com/click/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN///\"},\"assets\":[{\"id\":0,\"title\":{\"text\":\"4 Signs Your Heart Is Quietly Failing You\"},\"required\":1},{\"id\":3,\"img\":{\"w\":1200,\"h\":627,\"url\":\"https://de9a11s35xj3d.cloudfront.net/5922785fd53de8084607850abdaace4f.jpg\"}},{\"id\":4,\"data\":{\"value\":\"PhysioTru\"}},{\"id\":6,\"data\":{\"value\":\"\\n How To Avoid A Heart Attack (Do This For 7 Seconds Twice A Day)\\n \"}}],\"imptrackers\":[\"https://rtb-use.mfadsrvr.com/imp_c2s/v1/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN/${AUCTION_PRICE}\"],\"ver\":1}}", - "adomain": [ - "advertiserdomain.com" - ], - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "{\"native\":{\"link\":{\"url\":\"https://rtb-use.mfadsrvr.com/click/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN///\"},\"assets\":[{\"id\":0,\"title\":{\"text\":\"4 Signs Your Heart Is Quietly Failing You\"},\"required\":1},{\"id\":3,\"img\":{\"w\":1200,\"h\":627,\"url\":\"https://de9a11s35xj3d.cloudfront.net/5922785fd53de8084607850abdaace4f.jpg\"}},{\"id\":4,\"data\":{\"value\":\"PhysioTru\"}},{\"id\":6,\"data\":{\"value\":\"\\n How To Avoid A Heart Attack (Do This For 7 Seconds Twice A Day)\\n \"}}],\"imptrackers\":[\"https://rtb-use.mfadsrvr.com/imp_c2s/v1/ESoNneAwqCPnn97YSh0EoJzPUnSmqwdERCYPCdrHr1_TJz_V-x2xjMgxcROeooIH5fe1exAsWt2aqg1ESQEVQM8i0TpI1QBcV4V87Uzmf_XfAR6-6xqvqfGuDs-pJDWqAYz0P0OtHlrvVztlMdWu6JT9_GAtVAnB9gp0JchRJLSqr1h_GRZwuNUri7NvveTD7m8ZUHKNFldKPwHCbom120NFFn2Z3a6v0owsZfIgOff-1YyvZ9WkzVr3755kGRT_D1FUy3r2kurY8HdfeTiRuZAajluniEkJql7yGlS6hVfQ3vT3X93BKIo1F_A3o4bfywT49tM-3l2X8vwlc-w9X-B5VudQPJ8kboJZ2OuaD5AN/${AUCTION_PRICE}\"],\"ver\":1}}", - "adomain": ["advertiserdomain.com"], - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "native" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/exemplary/video.json b/adapters/engagebdr/engagebdrtest/exemplary/video.json deleted file mode 100644 index 53c00dc4523..00000000000 --- a/adapters/engagebdr/engagebdrtest/exemplary/video.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99998" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99998", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "video": { - "w": 300, - "mimes": null, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99998" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain":[ - "advertiserdomain.com" - ], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "\nStatic VASTStatic VAST Tag", - "adomain": ["advertiserdomain.com"], - "iurl": "https://cdn0.bnmla.com/vtest.xml", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "video" - } - ] - - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/audio.json b/adapters/engagebdr/engagebdrtest/supplemental/audio.json deleted file mode 100644 index e03fdb50aa4..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/audio.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "audio": { - }, - "ext": { - "bidder": { - "sspid": "99996" - } - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, invalid MediaType. EngageBDR only supports Banner, Video and Native.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json b/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json deleted file mode 100644 index a2cd79c9deb..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/multi-1-invalid-imp.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "w": 320, - "h": 50 - }, - "ext": { - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id-2, error while decoding impExt, err: unexpected end of JSON input.", - "comparison": "literal" - } - ], - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id" : "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": [ - "advertiserdomain.com" - ], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - } - ], - "seat": "test-request-id" - } - ], - "bidid": "test-request-id", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 9.81, - "adid": "abcde-12345", - "adm": "
", - "adomain": ["advertiserdomain.com"], - "iurl": "http://match.bnmla.com/usersync?sspid=59&redir=", - "cid": "campaign1", - "crid": "abcde-12345", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json deleted file mode 100644 index 9aa8177fc8b..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext-bidder.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: unexpected end of JSON input.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json deleted file mode 100644 index 04e167fd671..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-ext.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: unexpected end of JSON input.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json deleted file mode 100644 index d193bf779fc..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp-sspid.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - } - } - } - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Ignoring imp id=test-imp-id, no sspid present.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json b/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json deleted file mode 100644 index c5b0fa96042..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/no-imp.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "Invalid BidRequest. No valid imp.", - "comparison": "literal" - } - ] -} - diff --git a/adapters/engagebdr/engagebdrtest/supplemental/response-500.json b/adapters/engagebdr/engagebdrtest/supplemental/response-500.json deleted file mode 100644 index ce750770a63..00000000000 --- a/adapters/engagebdr/engagebdrtest/supplemental/response-500.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid": "99999" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://dsp.bnmla.com/hb?zoneid=99999", - "body":{ - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "sspid":"99999" - } - } - }] - } - }, - "mockResponse": { - "status": 500 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} - diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go index 8342da63ec2..16c30296b83 100644 --- a/adapters/eplanning/eplanning.go +++ b/adapters/eplanning/eplanning.go @@ -13,10 +13,10 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "strconv" ) diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go index c4a33e54c3d..44ab91413d4 100644 --- a/adapters/eplanning/eplanning_test.go +++ b/adapters/eplanning/eplanning_test.go @@ -3,10 +3,10 @@ package eplanning import ( "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/epom/epom.go b/adapters/epom/epom.go index a4e3040f079..37ef9543979 100644 --- a/adapters/epom/epom.go +++ b/adapters/epom/epom.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/epom/epom_test.go b/adapters/epom/epom_test.go index 6769ff6beb1..4e31c2ab982 100644 --- a/adapters/epom/epom_test.go +++ b/adapters/epom/epom_test.go @@ -3,9 +3,9 @@ package epom import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/flipp/flipp.go b/adapters/flipp/flipp.go index 9705fa574ba..e758ec741b2 100644 --- a/adapters/flipp/flipp.go +++ b/adapters/flipp/flipp.go @@ -1,6 +1,7 @@ package flipp import ( + "encoding/base64" "encoding/json" "fmt" "net/http" @@ -8,11 +9,13 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/gofrs/uuid" + "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/go-gdpr/vendorconsent" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/uuidutil" ) const ( @@ -22,6 +25,8 @@ const ( defaultCurrency = "USD" ) +var uuidGenerator uuidutil.UUIDGenerator + var ( count int64 = 1 adTypes = []int64{4309, 641} @@ -34,6 +39,7 @@ type adapter struct { // Builder builds a new instance of the Flipp adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + uuidGenerator = uuidutil.UUIDRandomGenerator{} bidder := &adapter{ endpoint: config.Endpoint, } @@ -114,9 +120,7 @@ func (a *adapter) processImp(request *openrtb2.BidRequest, imp openrtb2.Imp) (*a } var userIP string - if flippExtParams.IP != "" { - userIP = flippExtParams.IP - } else if request.Device != nil && request.Device.IP != "" { + if request.Device != nil && request.Device.IP != "" { userIP = request.Device.IP } else { return nil, fmt.Errorf("no IP set in flipp bidder params or request device") @@ -125,14 +129,15 @@ func (a *adapter) processImp(request *openrtb2.BidRequest, imp openrtb2.Imp) (*a var userKey string if request.User != nil && request.User.ID != "" { userKey = request.User.ID - } else if flippExtParams.UserKey != "" { + } else if flippExtParams.UserKey != "" && paramsUserKeyPermitted(request) { userKey = flippExtParams.UserKey } else { - uid, err := uuid.NewV4() + + uid, err := uuidGenerator.Generate() if err != nil { return nil, fmt.Errorf("unable to generate user uuid. %v", err) } - userKey = uid.String() + userKey = uid } keywordsArray := strings.Split(request.Site.Keywords, ",") @@ -225,3 +230,38 @@ func buildBid(decision *InlineModel, impId string) *openrtb2.Bid { } return bid } + +func paramsUserKeyPermitted(request *openrtb2.BidRequest) bool { + if request.Regs != nil { + if request.Regs.COPPA == 1 { + return false + } + if request.Regs.GDPR != nil && *request.Regs.GDPR == 1 { + return false + } + } + if request.Ext != nil { + var extData struct { + TransmitEids *bool `json:"transmitEids,omitempty"` + } + if err := json.Unmarshal(request.Ext, &extData); err == nil { + if extData.TransmitEids != nil && !*extData.TransmitEids { + return false + } + } + } + if request.User != nil && request.User.Consent != "" { + data, err := base64.RawURLEncoding.DecodeString(request.User.Consent) + if err != nil { + return true + } + consent, err := vendorconsent.Parse(data) + if err != nil { + return true + } + if !consent.PurposeAllowed(consentconstants.ContentSelectionDeliveryReporting) { + return false + } + } + return true +} diff --git a/adapters/flipp/flipp_params.go b/adapters/flipp/flipp_params.go index 0eef61b4b89..11f00560ba4 100644 --- a/adapters/flipp/flipp_params.go +++ b/adapters/flipp/flipp_params.go @@ -1,6 +1,6 @@ package flipp -import "github.com/prebid/prebid-server/openrtb_ext" +import "github.com/prebid/prebid-server/v2/openrtb_ext" type CampaignRequestBodyUser struct { Key *string `json:"key"` diff --git a/adapters/flipp/flipp_test.go b/adapters/flipp/flipp_test.go index 79b79e20769..310b7e145d9 100644 --- a/adapters/flipp/flipp_test.go +++ b/adapters/flipp/flipp_test.go @@ -1,17 +1,29 @@ package flipp import ( + "encoding/json" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) +const fakeUuid = "30470a14-2949-4110-abce-b62d57304ad5" + +type TestUUIDGenerator struct{} + +func (TestUUIDGenerator) Generate() (string, error) { + return fakeUuid, nil +} + func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderFlipp, config.Adapter{ Endpoint: "http://example.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + uuidGenerator = TestUUIDGenerator{} if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -19,3 +31,45 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "flipptest", bidder) } + +func TestParamsUserKeyPermitted(t *testing.T) { + + t.Run("Coppa is in effect", func(t *testing.T) { + request := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + COPPA: 1, + }, + } + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because coppa is in effect") + }) + t.Run("The Global Privacy Control is set", func(t *testing.T) { + request := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: openrtb2.Int8Ptr(1), + }, + } + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because Global Privacy Control is set") + }) + t.Run("The Prebid transmitEids activity is disallowed", func(t *testing.T) { + extData := struct { + TransmitEids bool `json:"transmitEids"` + }{ + TransmitEids: false, + } + ext, err := json.Marshal(extData) + if err != nil { + t.Fatalf("failed to marshal ext data: %v", err) + } + request := &openrtb2.BidRequest{ + Ext: ext, + } + + result := paramsUserKeyPermitted(request) + assert.New(t) + assert.False(t, result, "param user key not permitted because Prebid transmitEids activity is disallowed") + }) +} diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json new file mode 100644 index 00000000000..77ba527b5cf --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "user": { + }, + "ext": { + "transmitEids": false + }, + "regs": { + "coppa": 0, + "gdpr": 0 + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json new file mode 100644 index 00000000000..f61f6d9d710 --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json @@ -0,0 +1,177 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "regs": { + "coppa": 1 + }, + "user": { + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json new file mode 100644 index 00000000000..446ecd7d3f1 --- /dev/null +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json @@ -0,0 +1,177 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 1, + "device": { + "ip": "123.123.123.123" + }, + "site": { + "id": "1243066", + "page": "http://www.example.com/test?flipp-content-code=publisher-test" + }, + "user": { + }, + "regs": { + "gdpr": 1 + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 1800 + } + ] + }, + "ext": { + "bidder": { + "publisherNameIdentifier": "wishabi-test-publisher", + "creativeType": "NativeX", + "siteId": 1243066, + "zoneIds": [285431], + "options": { + "startCompact": true + }, + "userKey": "abc123" + } + } + } + + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/pserver", + "body": { + "ip":"123.123.123.123", + "keywords":[ + "" + ], + "placements":[ + { + "adTypes":[ + 4309, + 641 + ], + "count":1, + "divName":"inline", + "prebid":{ + "creativeType":"NativeX", + "height":1800, + "publisherNameIdentifier":"wishabi-test-publisher", + "requestId":"test-imp-id", + "width":300 + }, + "properties":{ + "contentCode":"publisher-test" + }, + "siteId":1243066, + "zoneIds":[ + 285431 + ], + "options": { + "startCompact": true + } + } + ], + "url":"http://www.example.com/test?flipp-content-code=publisher-test", + "user":{ + "key":"30470a14-2949-4110-abce-b62d57304ad5" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "decisions": { + "inline": [ + { + "adId": 183599115, + "advertiserId": 1988027, + "campaignId": 63285392, + "clickUrl": "https://e-11090.adzerk.net/r?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMiwicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsInVyIjoiaHR0cDovL3d3dy5mbGlwcC5jb20ifQ&s=Mnss8P1kc37Eftik5RJvLJb4S9Y", + "contents": [ + { + "data": { + "customData": { + "campaignConfigUrl": "https://campaign-config.flipp.com/dist-campaign-admin/215", + "campaignNameIdentifier": "US Grocery Demo (Kroger)" + }, + "height": 1800, + "width": 300 + }, + "type": "raw" + } + ], + "creativeId": 81325690, + "flightId": 175421795, + "height": 1800, + "impressionUrl": "https://e-11090.adzerk.net/i.gif?e=eyJ2IjoiMS4xMSIsImF2IjoxOTg4MDI3LCJhdCI6NDMwOSwiYnQiOjAsImNtIjo2MzI4NTM5MiwiY2giOjU4MDgxLCJjayI6e30sImNyIjo4MTMyNTY5MCwiZGkiOiJiOTg3MGNkYTA5MTU0NDlmOTkwZGNkZTNmNjYyNGNhMyIsImRqIjowLCJpaSI6IjJmNjYwMjMyODBmYjQ4NTRiYTY0YzFlYzA1ZDU5MTNiIiwiZG0iOjMsImZjIjoxODM1OTkxMTUsImZsIjoxNzU0MjE3OTUsImlwIjoiMTQyLjE4MS41OC41MiIsIm53IjoxMDkyMiwicGMiOjAsIm9wIjowLCJlYyI6MCwiZ20iOjAsImVwIjpudWxsLCJwciI6MjMyNjk5LCJydCI6MywicnMiOjUwMCwic2EiOiIzNCIsInNiIjoiaS0wNDZjMWNlNWRjYmExMTVjNSIsInNwIjozNzIzMDU1LCJzdCI6MTI0MzA2NiwidWsiOiJkOTU1N2Q2NS1kNWI5LTQyOTItYjg2My0xNGEyOTcyNTk3ZjQiLCJ6biI6Mjg1NDMxLCJ0cyI6MTY4MDU1NTc4MzkyMywicG4iOiJpbmxpbmUiLCJnYyI6dHJ1ZSwiZ0MiOnRydWUsImdzIjoibm9uZSIsInR6IjoiQW1lcmljYS9OZXdfWW9yayIsImJhIjoxLCJmcSI6MH0&s=Qce4_IohtESeNA_sB71Qjb4TouY", + "prebid": { + "cpm": 12.34, + "creative":"creativeContent", + "creativeType": "NativeX", + "requestId": "test-imp-id" + }, + "priorityId": 232699, + "storefront": { + "campaignConfig": { + "fallbackImageUrl": "https://f.wishabi.net/arbitrary_files/115856/1666018811/115856_Featured Local Savings - Flipp Fallback - US (1).png", + "fallbackLinkUrl": "https://flipp.com/flyers/", + "tags": { + "advertiser": { + "engagement": "https://f.wishabi.net/creative/happyfruits/_itemlevelv2/apple.png?cb=[[random]]" + } + } + }, + "flyer_id": 5554080, + "flyer_run_id": 873256, + "flyer_type_id": 2826, + "is_fallback": true, + "merchant": "Kroger", + "merchant_id": 2778, + "name": "Weekly Ad", + "storefront_logo_url": "https://images.wishabi.net/merchants/qX1/BGIzc9sFcA==/RackMultipart20210421-1-e3k5rx.jpeg", + "storefront_payload_url": "https://cdn-gateflipp.flippback.com/storefront-payload/v2/873256/5554080/ff14f675705934507c269b5750e124a323bc9bf60e8a6f210f422f4528b233ff?merchant_id=2778", + "valid_from": "2023-03-29 04:00:00 +0000 UTC", + "valid_to": "2023-04-05 03:59:59 +0000 UTC" + }, + "width": 300 + } + ] + }, + "location": { + "accuracy_radius": 5, + "city": "Toronto", + "country": "CA", + "ip": "123.123.123.123", + "postal_code": "M4S", + "region": "ON", + "region_name": "Ontario" + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "183599115", + "impid": "test-imp-id", + "price": 12.34, + "adm": "creativeContent", + "crid": "81325690", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json index 272bcd744ab..21957ae7096 100644 --- a/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native-param-user.json @@ -10,6 +10,11 @@ "page": "http://www.example.com/test?flipp-content-code=publisher-test" }, "user": { + "consent": "CP0xeHPP0xeHPBcABBENAgCAAPAAAAAAAGcgAAAAAAAA" + }, + "regs": { + "coppa": 0, + "gdpr": 0 }, "imp": [ { diff --git a/adapters/flipp/flipptest/exemplary/simple-banner-native.json b/adapters/flipp/flipptest/exemplary/simple-banner-native.json index c242c323003..d8a24131c77 100644 --- a/adapters/flipp/flipptest/exemplary/simple-banner-native.json +++ b/adapters/flipp/flipptest/exemplary/simple-banner-native.json @@ -30,7 +30,6 @@ "creativeType": "NativeX", "siteId": 1243066, "zoneIds": [285431], - "ip": "123.123.123.124", "options": { "startCompact": true, "contentCode": "publisher-test-2" @@ -46,7 +45,7 @@ "expectedRequest": { "uri": "http://example.com/pserver", "body": { - "ip":"123.123.123.124", + "ip":"123.123.123.123", "keywords":[ "" ], diff --git a/adapters/flipp/params_test.go b/adapters/flipp/params_test.go index 56f2fadbbdd..37f1fda6324 100644 --- a/adapters/flipp/params_test.go +++ b/adapters/flipp/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/freewheelssp/freewheelssp.go b/adapters/freewheelssp/freewheelssp.go index 06f975b3501..1a1738a4ef7 100644 --- a/adapters/freewheelssp/freewheelssp.go +++ b/adapters/freewheelssp/freewheelssp.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/freewheelssp/freewheelssp_test.go b/adapters/freewheelssp/freewheelssp_test.go index 5f06a29c2fd..ea1b5b7c980 100644 --- a/adapters/freewheelssp/freewheelssp_test.go +++ b/adapters/freewheelssp/freewheelssp_test.go @@ -1,10 +1,11 @@ package freewheelssp import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json index 5ec36993375..e6838240ca4 100644 --- a/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json +++ b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json @@ -14,7 +14,7 @@ }, "ext": { "bidder": { - "zoneId": 12345 + "zoneId": "12345" } } }, diff --git a/adapters/freewheelssp/freewheelssptest/exemplary/string-single-imp.json b/adapters/freewheelssp/freewheelssptest/exemplary/string-single-imp.json new file mode 100644 index 00000000000..c17c7167d6e --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/exemplary/string-single-imp.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "12345" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://testjsonsample.com", + "body":{ + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [{ + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "zoneId": 12345 + } + }] + }, + "headers": { + "Componentid": [ + "prebid-go" + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "freewheelssp-test", + "seatbid": [ + { + "bid": [ + { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + } + ], + "seat": "freewheelsspTv" + } + ], + "bidid": "freewheelssp-test", + "cur": "EUR" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "CUR", + "bids": [ + { + "bid": { + "id": "12345_freewheelssp-test_1", + "impid": "imp-1", + "price": 1.0, + "adid": "7857", + "adm": "", + "cid": "4001", + "crid": "7857" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/freewheelssp/freewheelssptest/supplemental/zoneid_error.json b/adapters/freewheelssp/freewheelssptest/supplemental/zoneid_error.json new file mode 100644 index 00000000000..b45296ab32c --- /dev/null +++ b/adapters/freewheelssp/freewheelssptest/supplemental/zoneid_error.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "freewheelssp-test", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-1", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "abc123" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": ".*Invalid imp.ext for impression index 0.*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/frvradn/frvradn.go b/adapters/frvradn/frvradn.go index 4088bf9cdf6..6781dbc50cf 100644 --- a/adapters/frvradn/frvradn.go +++ b/adapters/frvradn/frvradn.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/frvradn/frvradn_test.go b/adapters/frvradn/frvradn_test.go index f50f319b04f..89f3dc50b42 100644 --- a/adapters/frvradn/frvradn_test.go +++ b/adapters/frvradn/frvradn_test.go @@ -1,12 +1,13 @@ package frvradn import ( - "github.com/stretchr/testify/assert" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/frvradn/params_test.go b/adapters/frvradn/params_test.go index 2d4836c1e13..74a51b26fa2 100644 --- a/adapters/frvradn/params_test.go +++ b/adapters/frvradn/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go index 37862424ac7..fac38735507 100644 --- a/adapters/gamma/gamma.go +++ b/adapters/gamma/gamma.go @@ -9,10 +9,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type GammaAdapter struct { diff --git a/adapters/gamma/gamma_test.go b/adapters/gamma/gamma_test.go index 08709364ce9..78cde32984e 100644 --- a/adapters/gamma/gamma_test.go +++ b/adapters/gamma/gamma_test.go @@ -3,9 +3,9 @@ package gamma import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/gamma/params_test.go b/adapters/gamma/params_test.go index 56f1b591190..7d2d211b054 100644 --- a/adapters/gamma/params_test.go +++ b/adapters/gamma/params_test.go @@ -4,12 +4,10 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/gamma.json -// -// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.brightroll // TestValidParams makes sure that the Gamma schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { diff --git a/adapters/gamoshi/gamoshi.go b/adapters/gamoshi/gamoshi.go index d130ce9639a..db066364bf0 100644 --- a/adapters/gamoshi/gamoshi.go +++ b/adapters/gamoshi/gamoshi.go @@ -6,12 +6,11 @@ import ( "net/http" "strconv" - "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type GamoshiAdapter struct { @@ -48,7 +47,6 @@ func (a *GamoshiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada err := &errortypes.BadInput{ Message: fmt.Sprintf("Gamoshi only supports banner and video media types. Ignoring imp id=%s", request.Imp[i].ID), } - glog.Warning("Gamoshi SUPPORT VIOLATION: only banner and video media types supported") errs = append(errs, err) request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) i-- diff --git a/adapters/gamoshi/gamoshi_test.go b/adapters/gamoshi/gamoshi_test.go index 979f54cddab..4c787aeb700 100644 --- a/adapters/gamoshi/gamoshi_test.go +++ b/adapters/gamoshi/gamoshi_test.go @@ -3,9 +3,9 @@ package gamoshi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamplesWithConfiguredURI(t *testing.T) { diff --git a/adapters/gamoshi/params_test.go b/adapters/gamoshi/params_test.go index b9659aaa68a..6fb5d9ee08b 100644 --- a/adapters/gamoshi/params_test.go +++ b/adapters/gamoshi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/gamoshi.json diff --git a/adapters/globalsun/globalsun.go b/adapters/globalsun/globalsun.go index 6c381fbc0e4..3406f36b704 100644 --- a/adapters/globalsun/globalsun.go +++ b/adapters/globalsun/globalsun.go @@ -7,10 +7,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/globalsun/globalsun_test.go b/adapters/globalsun/globalsun_test.go index 0237422dc84..a2f33770486 100644 --- a/adapters/globalsun/globalsun_test.go +++ b/adapters/globalsun/globalsun_test.go @@ -3,9 +3,9 @@ package globalsun import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/globalsun/params_test.go b/adapters/globalsun/params_test.go index af71ad516c2..87d6427ad8b 100644 --- a/adapters/globalsun/params_test.go +++ b/adapters/globalsun/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/gothamads/gothamads.go b/adapters/gothamads/gothamads.go new file mode 100644 index 00000000000..301a4613677 --- /dev/null +++ b/adapters/gothamads/gothamads.go @@ -0,0 +1,170 @@ +package gothamads + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + return bidder, nil +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return headers +} + +func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + impExt, err := getImpressionExt(&openRTBRequest.Imp[0]) + if err != nil { + return nil, []error{err} + } + + openRTBRequest.Imp[0].Ext = nil + + url, err := a.buildEndpointURL(impExt) + if err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + Headers: getHeaders(openRTBRequest), + }}, nil +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtGothamAds, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + var gothamadsExt openrtb_ext.ExtGothamAds + if err := json.Unmarshal(bidderExt.Bidder, &gothamadsExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + return &gothamadsExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtGothamAds) (string, error) { + endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func checkResponseStatusCodes(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong Status Code: [ %d ] ", response.StatusCode), + } + } + + return adapters.CheckResponseStatusCodeForErrors(response) +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { + if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) { + return nil, nil + } + + httpStatusError := checkResponseStatusCodes(bidderRawResponse) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + var bidsArray []*adapters.TypedBid + + for _, sb := range bidResp.SeatBid { + for idx, bid := range sb.Bid { + bidType, err := getMediaTypeForImp(bid) + if err != nil { + return nil, []error{err} + } + + bidsArray = append(bidsArray, &adapters.TypedBid{ + Bid: &sb.Bid[idx], + BidType: bidType, + }) + } + } + + bidResponse.Bids = bidsArray + return bidResponse, nil +} + +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unsupported MType %d", bid.MType) + } +} diff --git a/adapters/gothamads/gothamads_test.go b/adapters/gothamads/gothamads_test.go new file mode 100644 index 00000000000..5dde1527f3f --- /dev/null +++ b/adapters/gothamads/gothamads_test.go @@ -0,0 +1,28 @@ +package gothamads + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderGothamads, config.Adapter{ + Endpoint: "http://us-e-node1.gothamads.com/?pass={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "gothamadstest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderGothamads, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/gothamads/gothamadstest/exemplary/banner-app.json b/adapters/gothamads/gothamadstest/exemplary/banner-app.json new file mode 100644 index 00000000000..44966ea8888 --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/banner-app.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "1", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "type": "banner", + "seat": "gothamAds" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/exemplary/banner-web.json b/adapters/gothamads/gothamadstest/exemplary/banner-web.json new file mode 100644 index 00000000000..baac33a4f8c --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/banner-web.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + }, + { + "id": "some-impression-id2", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + } + }, + { + "id": "some-impression-id2", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + { + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "some-impression-id2", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "type": "banner", + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id1", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "some-impression-id2", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/exemplary/native-app.json b/adapters/gothamads/gothamadstest/exemplary/native-app.json new file mode 100644 index 00000000000..5c54d526699 --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/native-app.json @@ -0,0 +1,147 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "mtype": 4 + } + ], + "type": "native", + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/exemplary/native-web.json b/adapters/gothamads/gothamadstest/exemplary/native-web.json new file mode 100644 index 00000000000..a2f16e78606 --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/native-web.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "mtype": 4 + } + ], + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/exemplary/video-app.json b/adapters/gothamads/gothamadstest/exemplary/video-app.json new file mode 100644 index 00000000000..a08c5a032c1 --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/video-app.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "mtype": 2 + } + ], + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/exemplary/video-web.json b/adapters/gothamads/gothamadstest/exemplary/video-web.json new file mode 100644 index 00000000000..64293e740ba --- /dev/null +++ b/adapters/gothamads/gothamadstest/exemplary/video-web.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + } + ], + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/bad_media_type.json b/adapters/gothamads/gothamadstest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..349ea5da3f4 --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/bad_media_type.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "1", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-imp-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "mtype": 0, + "seat": "gothamads" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unsupported MType 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/empty-seatbid-array.json b/adapters/gothamads/gothamadstest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..50eafd989af --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/empty-seatbid-array.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "mockResponse": { + "status": 200, + "body": "invalid response" + }, + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/invalid-bidder-ext-object.json b/adapters/gothamads/gothamadstest/supplemental/invalid-bidder-ext-object.json new file mode 100644 index 00000000000..e412d39ca2c --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/invalid-bidder-ext-object.json @@ -0,0 +1,33 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtGothamAds", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": "bidderExt" + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/invalid-gotham-ext-object.json b/adapters/gothamads/gothamadstest/supplemental/invalid-gotham-ext-object.json new file mode 100644 index 00000000000..6075e2a4b3d --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/invalid-gotham-ext-object.json @@ -0,0 +1,31 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "wrongExt" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/invalid-response.json b/adapters/gothamads/gothamadstest/supplemental/invalid-response.json new file mode 100644 index 00000000000..eb928b41551 --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/invalid-response.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/status-code-bad-request.json b/adapters/gothamads/gothamadstest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..37c047edaa3 --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/status-code-bad-request.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/status-code-no-content.json b/adapters/gothamads/gothamadstest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..76a3a4fc84d --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/status-code-no-content.json @@ -0,0 +1,75 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/status-code-other-error.json b/adapters/gothamads/gothamadstest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..45a0f67f3e0 --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/status-code-other-error.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 306. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/gothamads/gothamadstest/supplemental/status-code-service-unavailable.json b/adapters/gothamads/gothamadstest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..97773cd0d53 --- /dev/null +++ b/adapters/gothamads/gothamadstest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://us-e-node1.gothamads.com/?pass=accountId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/engagebdr/params_test.go b/adapters/gothamads/params_test.go similarity index 67% rename from adapters/engagebdr/params_test.go rename to adapters/gothamads/params_test.go index c797d04ecc8..1db984321e1 100644 --- a/adapters/engagebdr/params_test.go +++ b/adapters/gothamads/params_test.go @@ -1,12 +1,16 @@ -package engagebdr +package gothamads import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) +var validParams = []string{ + `{"accountId": "hash"}`, +} + func TestValidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -14,12 +18,24 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderEngageBDR, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected beachfront params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderGothamads, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Gothamads params: %s", validParam) } } } +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "accountid": "" }`, +} + func TestInvalidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -27,24 +43,8 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderEngageBDR, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderGothamads, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } } - -var validParams = []string{ - `{"sspid":"12345"}`, -} - -var invalidParams = []string{ - ``, - `null`, - `true`, - `5`, - `4.2`, - `[]`, - `{}`, - `{"sspid":null}`, - `{"appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76"}`, -} diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index 4f44f90ab11..0d6bdd76d9b 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -8,11 +8,11 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/maputil" ) type GridAdapter struct { diff --git a/adapters/grid/grid_test.go b/adapters/grid/grid_test.go index 38e9341cdaf..f35f0c66582 100644 --- a/adapters/grid/grid_test.go +++ b/adapters/grid/grid_test.go @@ -3,9 +3,9 @@ package grid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index a500376e72a..ea9624c73c7 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -8,19 +8,19 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -// GumGumAdapter implements Bidder interface. -type GumGumAdapter struct { +// adapter implements Bidder interface. +type adapter struct { URI string } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (g *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var validImps []openrtb2.Imp var siteCopy openrtb2.Site if request.Site != nil { @@ -80,7 +80,7 @@ func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap } // MakeBids unpacks the server's response into Bids. -func (g *GumGumAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (g *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -176,6 +176,14 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { } } + if gumgumExt.Product != "" { + var err error + imp.Ext, err = json.Marshal(map[string]string{"product": gumgumExt.Product}) + if err != nil { + return nil, err + } + } + return &gumgumExt, nil } @@ -223,7 +231,7 @@ func validateVideoParams(video *openrtb2.Video) (err error) { // Builder builds a new instance of the GumGum adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &GumGumAdapter{ + bidder := &adapter{ URI: config.Endpoint, } return bidder, nil diff --git a/adapters/gumgum/gumgum_test.go b/adapters/gumgum/gumgum_test.go index fa78eb10e11..621b4a96b04 100644 --- a/adapters/gumgum/gumgum_test.go +++ b/adapters/gumgum/gumgum_test.go @@ -3,9 +3,9 @@ package gumgum import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json b/adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json similarity index 53% rename from adapters/brightroll/brightrolltest/exemplary/simple-banner.json rename to adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json index f59038503cf..6e2793658d3 100644 --- a/adapters/brightroll/brightrolltest/exemplary/simple-banner.json +++ b/adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json @@ -12,39 +12,31 @@ }, { "w": 300, - "h": 600 + "h": 300 } - ] + ], + "w": 300, + "h": 250 }, "ext": { "bidder": { - "publisher": "adthrive" - } + "pubId": 12345678 + }, + "product": "skins" } } ] }, - "httpCalls": [ { "expectedRequest": { - "uri": "http://test-bid.ybp.yahoo.com/bid/appnexuspbs?publisher=adthrive", + "uri": "https://g2.gumgum.com/providers/prbds2s/bid", "body": { "id": "test-request-id", - "at":1, - "bcat": [ - "IAB8-5", - "IAB8-18" - ], "imp": [ { "id": "test-imp-id", "banner": { - "battr": [ - 1, - 2, - 3 - ], "format": [ { "w": 300, @@ -52,7 +44,7 @@ }, { "w": 300, - "h": 600 + "h": 300 } ], "w": 300, @@ -60,8 +52,9 @@ }, "ext": { "bidder": { - "publisher": "adthrive" - } + "pubId": 12345678 + }, + "product": "skins" } } ] @@ -70,50 +63,42 @@ "mockResponse": { "status": 200, "body": { - "id": "test-request-id", "seatbid": [ { - "seat": "958", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300 - }] + "bid": [ + { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + } + ] } - ], - "bidid": "5778926625248726496", - "cur": "USD" + ] } } } ], - "expectedBidResponses": [ { + "currency": "USD", "bids": [ { "bid": { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, + "crid": "2068416", "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250 + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" }, "type": "banner" } ] } ] -} +} \ No newline at end of file diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go index a578ce65762..11874cdbd61 100644 --- a/adapters/gumgum/params_test.go +++ b/adapters/gumgum/params_test.go @@ -2,8 +2,9 @@ package gumgum import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { @@ -36,11 +37,13 @@ var validParams = []string{ `{"zone":"dc9d6be1"}`, `{"pubId":12345678}`, `{"zone":"dc9d6be1", "pubId":12345678}`, + `{"zone":"dc9d6be1", "pubId":12345678, "product": "skins"}`, `{"zone":"dc9d6be1", "slot":1234567}`, `{"pubId":12345678, "slot":1234567}`, `{"pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, + `{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5", "product": "skins"}`, } var invalidParams = []string{ @@ -61,4 +64,6 @@ var invalidParams = []string{ `{"zone":"1234567", "irisid": ""}`, `{"zone":"1234567", "irisid": 1234}`, `{"irisid": "iris_6f9285823a48bne5"}`, + `{"product": "test"}`, + `{"product": 12345678}`, } diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go index 2008eecd9f1..06ad88f42e6 100644 --- a/adapters/huaweiads/huaweiads.go +++ b/adapters/huaweiads/huaweiads.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "net/http" "net/url" "regexp" @@ -19,10 +20,10 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const huaweiAdxApiVersion = "3.4" @@ -185,6 +186,7 @@ type metaData struct { ApkInfo apkInfo `json:"apkInfo"` Duration int64 `json:"duration"` MediaFile mediaFile `json:"mediaFile"` + Cta string `json:"cta"` } type imageInfo struct { @@ -532,49 +534,94 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { return err } + //popular size for native ads + popularSizes := []format{{W: 225, H: 150}, {W: 1080, H: 607}, {W: 300, H: 250}, {W: 1080, H: 1620}, {W: 1280, H: 720}, {W: 640, H: 360}, {W: 1080, H: 1920}, {W: 720, H: 1280}} + // only compute the main image number, type = native1.ImageAssetTypeMain var numMainImage = 0 var numVideo = 0 - var width int64 - var height int64 + var formats = make([]format, 0) + var numFormat = 0 + var detailedCreativeTypeList = make([]string, 0, 2) + + //number of the requested image size + for _, asset := range nativePayload.Assets { + if numFormat > 1 { + break + } + if asset.Img != nil { + if asset.Img.Type == native1.ImageAssetTypeMain { + numFormat++ + } + } + } + + sizeMap := make(map[format]struct{}) + for _, size := range popularSizes { + sizeMap[size] = struct{}{} + } + for _, asset := range nativePayload.Assets { // Only one of the {title,img,video,data} objects should be present in each object. if asset.Video != nil { numVideo++ - continue + formats = popularSizes + _, ok := sizeMap[format{W: asset.Video.W, H: asset.Video.H}] + if (asset.Video.W != 0 && asset.Video.H != 0) && !ok { + formats = append(formats, format{asset.Video.W, asset.Video.H}) + } } // every image has the same W, H. if asset.Img != nil { if asset.Img.Type == native1.ImageAssetTypeMain { numMainImage++ - if asset.Img.H != 0 && asset.Img.W != 0 { - width = asset.Img.W - height = asset.Img.H - } else if asset.Img.WMin != 0 && asset.Img.HMin != 0 { - width = asset.Img.WMin - height = asset.Img.HMin + if numFormat > 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + formats = append(formats, format{asset.Img.W, asset.Img.H}) + } + if numFormat == 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + result := filterPopularSizes(popularSizes, asset.Img.W, asset.Img.H, "ratio") + formats = append(formats, result...) + } + if numFormat == 1 && asset.Img.H == 0 && asset.Img.W == 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 { + result := filterPopularSizes(popularSizes, asset.Img.WMin, asset.Img.HMin, "range") + formats = append(formats, result...) } } - continue } + adslot30.Format = formats } - adslot30.W = width - adslot30.H = height - - var detailedCreativeTypeList = make([]string, 0, 2) if numVideo >= 1 { detailedCreativeTypeList = append(detailedCreativeTypeList, "903") - } else if numMainImage > 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "904") - } else if numMainImage == 1 { - detailedCreativeTypeList = append(detailedCreativeTypeList, "901") - } else { - detailedCreativeTypeList = append(detailedCreativeTypeList, "913", "914") + } + if numMainImage >= 1 { + detailedCreativeTypeList = append(detailedCreativeTypeList, "901", "904", "905") } adslot30.DetailedCreativeTypeList = detailedCreativeTypeList return nil } +// filter popular size by range or ratio to append format array +func filterPopularSizes(sizes []format, width int64, height int64, byWhat string) []format { + + filtered := []format{} + for _, size := range sizes { + w := size.W + h := size.H + + if byWhat == "ratio" { + ratio := float64(width) / float64(height) + diff := math.Abs(float64(w)/float64(h) - ratio) + if diff <= 0.5 { + filtered = append(filtered, size) + } + } + if byWhat == "range" && w > width && h > height { + filtered = append(filtered, size) + } + } + return filtered +} + // roll ad need TotalDuration func getVideoFormat(adslot30 *adslot30, adtype int32, openRTBImp *openrtb2.Imp) error { adslot30.W = openRTBImp.Video.W @@ -902,7 +949,6 @@ func getReqConsentInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRe if openRTBRequest.User != nil && openRTBRequest.User.Ext != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(openRTBRequest.User.Ext, &extUser); err != nil { - fmt.Errorf("failed to parse ExtUser in HuaweiAds GDPR check: %v", err) return } request.Consent = extUser.Consent @@ -960,10 +1006,14 @@ func checkRespStatusCode(response *adapters.ResponseData) error { } func checkHuaweiAdsResponseRetcode(response huaweiAdsResponse) error { - if response.Retcode == 200 || response.Retcode == 204 || response.Retcode == 206 { + if response.Retcode == 200 || response.Retcode == 206 { return nil } - + if response.Retcode == 204 { + return &errortypes.BadInput{ + Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: The request packet is correct, but no advertisement was found for this request.", response.Retcode), + } + } if (response.Retcode < 600 && response.Retcode >= 400) || (response.Retcode < 300 && response.Retcode > 200) { return &errortypes.BadInput{ Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: %s", response.Retcode, response.Reason), @@ -1139,6 +1189,9 @@ func (a *adapter) extractAdmNative(adType int32, content *content, bidType openr } responseAsset.Video = &videoObject } else if asset.Img != nil { + if len(content.MetaData.ImageInfo) == imgIndex && asset.Img.Type == native1.ImageAssetTypeMain { + continue + } var imgObject nativeResponse.Image imgObject.URL = "" imgObject.Type = asset.Img.Type @@ -1170,6 +1223,11 @@ func (a *adapter) extractAdmNative(adType int32, content *content, bidType openr dataObject.Label = "desc" dataObject.Value = getDecodeValue(content.MetaData.Description) } + + if asset.Data.Type == native1.DataAssetTypeCTAText { + dataObject.Type = native1.DataAssetTypeCTAText + dataObject.Value = getDecodeValue(content.MetaData.Cta) + } responseAsset.Data = &dataObject } var id = asset.ID diff --git a/adapters/huaweiads/huaweiads_test.go b/adapters/huaweiads/huaweiads_test.go index f523f0b9d94..9e7d0b55364 100644 --- a/adapters/huaweiads/huaweiads_test.go +++ b/adapters/huaweiads/huaweiads_test.go @@ -3,9 +3,9 @@ package huaweiads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json index 8a7ca1b0752..c38c4124981 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":1080,\"hmin\":607},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -92,12 +92,9 @@ { "adtype": 3, "slotid": "u42ohmaufh", - "detailedCreativeTypeList": [ - "903" - ], - "h": 200, - "test": 1, - "w": 200 + "detailedCreativeTypeList":["903","901","904","905"], + "format":[{"w":225,"h":150},{"w":1080,"h":607},{"w":300,"h":250},{"w":1080,"h":1620},{"w":1280,"h":720},{"w":640,"h":360},{"w":1080,"h":1920},{"w":720,"h":1280}], + "test": 1 } ], "device": { @@ -178,19 +175,11 @@ "imageInfo": [ { "checkSha256Flag": 0, - "height": 350, + "height": 607, "imageType": "img", "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", "url": "http://image1.jpg", - "width": 400 - }, - { - "checkSha256Flag": 0, - "height": 300, - "imageType": "img", - "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", - "url": "http://image2.jpg", - "width": 400 + "width": 1080 } ], "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", @@ -312,7 +301,7 @@ "huaweiads" ], "crid": "58022259", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":107,\"video\":{\"vasttag\":\"HuaweiAds/test/00:00:06.038 \"}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\",\"http://test/dspclick\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"},{\"event\":1,\"method\":1,\"url\":\"http://test/dspimp\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":1080,\"h\":607}},{\"id\":107,\"video\":{\"vasttag\":\"HuaweiAds/test/00:00:06.038 \"}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\",\"http://test/dspclick\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"},{\"event\":1,\"method\":1,\"url\":\"http://test/dspimp\"}]}", "id": "test-imp-id", "impid": "test-imp-id", "price": 2.8, diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json new file mode 100644 index 00000000000..97679a3bb1f --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json @@ -0,0 +1,306 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"wmin\":225,\"hmin\":150},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "dnt": 0, + "pxratio": 23.01, + "geo": { + "country": "DEU" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "DE", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "901", + "904", + "905" + ], + "format":[{"w":1080,"h":607},{"w":300,"h":250},{"w":1080,"h":1620},{"w":1280,"h":720},{"w":640,"h":360},{"w":1080,"h":1920},{"w":720,"h":1280}], + "test": 1 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "DE", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "gaidTrackingEnabled": "1", + "isTrackingEnabled": "1", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "DE" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 28, + "dspcost": 59, + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "ctrlSwitchs": "001011101001010212", + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appDesc": "43+%E4%BA%BF%E6%AC%A1%E5%AE%89%E8%A3%85", + "appIcon": "https://icon.png", + "appName": "demo", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "http://test/url", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "install", + "duration": 6038, + "description": "", + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 607, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image.jpg", + "width": 1080 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":1080,\"h\":607}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 607, + "w": 1080 + }, + "type": "native" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json new file mode 100644 index 00000000000..e9c50baea89 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json @@ -0,0 +1,306 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":225,\"wmin\":225,\"h\":150,\"hmin\":150},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1},{\"id\":104,\"data\":{\"type\":12,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "en", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "dnt": 0, + "pxratio": 23.01, + "geo": { + "country": "DEU" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://adx-dre.op.hicloud.com/ppsadx/getResult", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "DE", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "901", + "904", + "905" + ], + "format":[{"w":225,"h":150},{"w":1080,"h":607},{"w":300,"h":250},{"w":1280,"h":720},{"w":640,"h":360}], + "test": 1 + } + ], + "device": { + "height": 1920, + "language": "en", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "DE", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "gaidTrackingEnabled": "1", + "isTrackingEnabled": "1", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "DE" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4", + "clientAdRequestId": "test-req-id" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 28, + "dspcost": 59, + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "ctrlSwitchs": "001011101001010212", + "endtime": 1621344684645, + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appDesc": "43+%E4%BA%BF%E6%AC%A1%E5%AE%89%E8%A3%85", + "appIcon": "https://icon.png", + "appName": "demo", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "http://test/url", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "install", + "duration": 6038, + "description": "", + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 607, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image.jpg", + "width": 1080 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":1080,\"h\":607}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}},{\"id\":104,\"data\":{\"type\":12,\"value\":\"install\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 607, + "w": 1080 + }, + "type": "native" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json index 240098e038d..4ba3adce8a7 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1},{\"id\":105,\"data\":{\"type\":12,\"len\":90}}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -91,11 +91,12 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "901" + "901", + "904", + "905" ], - "h": 200, - "test": 1, - "w": 200 + "format":[{"w":1080,"h":607},{"w":300,"h":250},{"w":1080,"h":1620},{"w":1280,"h":720},{"w":640,"h":360},{"w":1080,"h":1920},{"w":720,"h":1280}], + "test": 1 } ], "device": { @@ -166,6 +167,7 @@ "appId": "101219405", "appPromotionChannel": "401721412", "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "install", "duration": 6038, "description": "", "icon": [ @@ -182,11 +184,11 @@ "imageInfo": [ { "checkSha256Flag": 0, - "height": 1280, + "height": 150, "imageType": "img", "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", "url": "http://image.jpg", - "width": 720 + "width": 225 } ], "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", @@ -300,12 +302,12 @@ "huaweiads" ], "crid": "58022259", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":720,\"h\":1280}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":225,\"h\":150}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}},{\"id\":105,\"data\":{\"type\":12,\"value\":\"install\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", "id": "test-imp-id", "impid": "test-imp-id", "price": 2.8, - "h": 1280, - "w": 720 + "h": 150, + "w": 225 }, "type": "native" } diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json index 9b46072dd00..240dee2d9d1 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":200,\"h\":200},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"w\":200,\"h\":300,\"wmin\":300,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":400,\"h\":500,\"wmin\":400,\"hmin\":500},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"w\":300,\"h\":250,\"wmin\":300,\"hmin\":250},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -90,11 +90,25 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "904" + "901", + "904", + "905" ], - "h": 200, - "test": 1, - "w": 200 + "format": [ + { + "w": 200, + "h": 300 + }, + { + "w": 400, + "h": 500 + }, + { + "w": 300, + "h": 250 + } + ], + "test": 1 } ], "device": { diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json index f78f50e2f0f..3f4222c2146 100644 --- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":1,\"w\":20,\"h\":20},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":1,\"w\":20,\"h\":20},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -91,11 +91,12 @@ "adtype": 3, "slotid": "u42ohmaufh", "detailedCreativeTypeList": [ - "904" + "901", + "904", + "905" ], - "h": 200, - "test": 1, - "w": 200 + "format":[{"w":1080,"h":607},{"w":300,"h":250},{"w":1080,"h":1620},{"w":1280,"h":720},{"w":640,"h":360},{"w":1080,"h":1920},{"w":720,"h":1280}], + "test": 1 } ], "device": { @@ -320,7 +321,7 @@ "huaweiads" ], "crid": "58022259", - "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":1,\"url\":\"https://icon1.png\",\"w\":160,\"h\":160}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image2.jpg\",\"w\":400,\"h\":300}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":1,\"url\":\"https://icon1.png\",\"w\":160,\"h\":160}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}", "id": "test-imp-id", "impid": "test-imp-id", "price": 2.8, diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json index c38bc9f69b9..10814d7eb9e 100644 --- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "native": { - "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", "ver": "1.2" }, "ext": { @@ -91,11 +91,43 @@ "adtype": 3, "slotid": "m8x9x3rzff", "test": 1, + "format": [ + { + "w": 225, + "h": 150 + }, + { + "w": 1080, + "h": 607 + }, + { + "w": 300, + "h": 250 + }, + { + "w": 1080, + "h": 1620 + }, + { + "w": 1280, + "h": 720 + }, + { + "w": 640, + "h": 360 + }, + { + "w": 1080, + "h": 1920 + }, + { + "w": 720, + "h": 1280 + } + ], "detailedCreativeTypeList": [ "903" - ], - "h": 200, - "w": 200 + ] } ], "device": { diff --git a/adapters/huaweiads/params_test.go b/adapters/huaweiads/params_test.go index 7347725b299..c93c3c3ac55 100644 --- a/adapters/huaweiads/params_test.go +++ b/adapters/huaweiads/params_test.go @@ -2,8 +2,9 @@ package huaweiads import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/imds/imds.go b/adapters/imds/imds.go index 02d30828966..c5ef45176ab 100644 --- a/adapters/imds/imds.go +++ b/adapters/imds/imds.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterVersion string = "pbs-go/1.0.0" diff --git a/adapters/imds/imds_test.go b/adapters/imds/imds_test.go index 5fab0509c1d..6e8165fbe6a 100644 --- a/adapters/imds/imds_test.go +++ b/adapters/imds/imds_test.go @@ -3,9 +3,9 @@ package imds import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/imds/params_test.go b/adapters/imds/params_test.go index e2052b4c262..740105c3183 100644 --- a/adapters/imds/params_test.go +++ b/adapters/imds/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/imds.json diff --git a/adapters/impactify/impactify.go b/adapters/impactify/impactify.go index bfbcdfbf8a2..73dc9450a3b 100644 --- a/adapters/impactify/impactify.go +++ b/adapters/impactify/impactify.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/impactify/impactify_test.go b/adapters/impactify/impactify_test.go index 550ea7ad498..b9518d5e2a3 100644 --- a/adapters/impactify/impactify_test.go +++ b/adapters/impactify/impactify_test.go @@ -3,9 +3,9 @@ package impactify import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/impactify/params_test.go b/adapters/impactify/params_test.go index 845ea29f00b..2fa8b24d627 100644 --- a/adapters/impactify/params_test.go +++ b/adapters/impactify/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index 4c64451f247..2cad15fa033 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -4,18 +4,18 @@ import ( "encoding/json" "fmt" "net/http" + "regexp" "strconv" "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( - buyingTypeRTB = "rtb" isRewardedInventory = "is_rewarded_inventory" stateRewardedInventoryEnable = "1" consentProvidersSettingsInputKey = "ConsentedProvidersSettings" @@ -43,6 +43,8 @@ type ImpExtBidder struct { } } +var dealDetectionRegEx = regexp.MustCompile("(classic|deal)") + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) @@ -119,6 +121,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } var bidResp openrtb2.BidResponse + var impMap = make(map[string]openrtb2.Imp) if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -134,17 +137,21 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } seatBid := bidResp.SeatBid[0] - if len(seatBid.Bid) == 0 { return nil, nil } bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid)) + bidResponse.Currency = bidResp.Cur + + for i := range internalRequest.Imp { + impMap[internalRequest.Imp[i].ID] = internalRequest.Imp[i] + } for i := range seatBid.Bid { bid := seatBid.Bid[i] - bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) + bidType, err := getBidType(bid, impMap) if err != nil { return nil, []error{err} } @@ -157,7 +164,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } bidExtImprovedigital := bidExt.Improvedigital - if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB { + if bidExtImprovedigital.LineItemID != 0 && dealDetectionRegEx.MatchString(bidExtImprovedigital.BuyingType) { bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID) } } @@ -168,7 +175,6 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e }) } return bidResponse, nil - } // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config. @@ -179,31 +185,73 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } - - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } +func getBidType(bid openrtb2.Bid, impMap map[string]openrtb2.Imp) (openrtb_ext.BidType, error) { + // there must be a matching imp against bid.ImpID + imp, found := impMap[bid.ImpID] + if !found { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", bid.ImpID), + } + } - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil + // if MType is not set in server response, try to determine it + if bid.MType == 0 { + if !isMultiFormatImp(imp) { + // Not a bid for multi format impression. So, determine MType from impression + if imp.Banner != nil { + bid.MType = openrtb2.MarkupBanner + } else if imp.Video != nil { + bid.MType = openrtb2.MarkupVideo + } else if imp.Audio != nil { + bid.MType = openrtb2.MarkupAudio + } else if imp.Native != nil { + bid.MType = openrtb2.MarkupNative + } else { // This should not happen. + // Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Could not determine MType from impression with ID: \"%s\"", bid.ImpID), + } } - + } else { return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + Message: fmt.Sprintf("Bid must have non-zero MType for multi format impression with ID: \"%s\"", bid.ImpID), } } } - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + // map MType to BidType + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + // This shouldn't happen. Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d for impression with ID: \"%s\"", bid.MType, bid.ImpID), + } + } +} + +func isMultiFormatImp(imp openrtb2.Imp) bool { + formatCount := 0 + if imp.Banner != nil { + formatCount++ + } + if imp.Video != nil { + formatCount++ + } + if imp.Audio != nil { + formatCount++ + } + if imp.Native != nil { + formatCount++ } + return formatCount > 1 } // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found diff --git a/adapters/improvedigital/improvedigital_test.go b/adapters/improvedigital/improvedigital_test.go index 15ca5d5f033..b89e03320fa 100644 --- a/adapters/improvedigital/improvedigital_test.go +++ b/adapters/improvedigital/improvedigital_test.go @@ -3,9 +3,9 @@ package improvedigital import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json index 6963e82f9c4..425435049db 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json @@ -40,6 +40,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } } ] }, @@ -159,6 +181,71 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "appID", + "publisher": { + "id": "uniq_pub_id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], "expectedBidResponses": [ @@ -197,6 +284,24 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/audio.json b/adapters/improvedigital/improvedigitaltest/exemplary/audio.json new file mode 100644 index 00000000000..9ddbf799517 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/exemplary/audio.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { "asi": "example.com", "hp": 1, "sid": "1234abc" } + ] + } + } + }, + "tmax": 1000, + "imp": [ + { + "id": "audio", + "audio": { + "mimes": ["audio/mp3"], + "protocols": [2, 5] + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + }, + "site": { + "publisher": { "id": "pubid" }, + "page": "example.com" + }, + "device": { "w": 1581, "h": 922, "ip": "1.1.1.1" }, + "regs": { "ext": { "gdpr": 0 } }, + "user": { + "ext": { + "consent": "XYZ" + } + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "imp": [ + { + "id": "audio", + "audio": { + "mimes": ["audio/mp3"], + "protocols": [2, 5] + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ], + "site": { + "page": "example.com", + "publisher": { "id": "pubid" } + }, + "device": { + "ip": "1.1.1.1", + "h": 922, + "w": 1581 + }, + "user": { + "ext": { + "consent": "XYZ" + } + }, + "tmax": 1000, + "source": { + "tid": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "hp": 1, + "sid": "1234abc" + } + ] + } + } + }, + "regs": { "ext": { "gdpr": 0 } }, + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "26f14780-8ef8-4f41-b70c-c4d062237df6", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + }, + "crid": "14065", + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "price": 0.01, + "adm": "some-test-ad-vast", + "adomain": ["example.com"], + "impid": "audio", + "cid": "25076" + } + ], + "seat": "improvedigital" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "d4f04449-ba04-4d7c-bb34-dc0fc5240f59", + "impid": "audio", + "price": 0.01, + "adm": "some-test-ad-vast", + "adomain": ["example.com"], + "cid": "25076", + "crid": "14065", + "ext": { + "improvedigital": { + "brand_name": "AdvertiserABC", + "bidder_id": 301 + } + } + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/native.json b/adapters/improvedigital/improvedigitaltest/exemplary/native.json index 3309b35a753..3865d40383b 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/native.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/native.json @@ -17,7 +17,11 @@ "imp": [ { "id": "native", - "ext": { "improvedigital": { "placementId": 1234 } }, + "ext": { + "bidder": { + "placementId": 1234 + } + }, "native": { "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" @@ -57,7 +61,11 @@ "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" }, - "ext": { "improvedigital": { "placementId": 1234 } } + "ext": { + "bidder": { + "placementId": 1234 + } + } } ], "site": { diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json index 0945b4c0f69..d7131a54577 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json @@ -42,6 +42,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } } ] }, @@ -166,6 +188,75 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "domain": "good.site", + "publisher": { + "id": "uniq_pub_id" + }, + "keywords": "omgword", + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], @@ -205,6 +296,26 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json b/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json new file mode 100644 index 00000000000..633e8b6b0aa --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "cur": ["EUR"], + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "cur": ["EUR"], + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "EUR", + "seatbid": [{ + "seat": "improvedigital", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }] + } + } + }], + + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json new file mode 100644 index 00000000000..ad82321a401 --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json @@ -0,0 +1,215 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + }, + { + "id": "test-unsupported-format-id-without-mtype", + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "improvedigital", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 5 + }] + }], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-without-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-unsupported-format-id-without-mtype", + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid3", + "impid": "test-unsupported-format-id-without-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 5 for impression with ID: \"test-imp-id\"", + "comparison": "literal" + }, + { + "value": "Bid must have non-zero MType for multi format impression with ID: \"test-multi-format-id-without-mtype\"", + "comparison": "literal" + }, + { + "value": "Could not determine MType from impression with ID: \"test-unsupported-format-id-without-mtype\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/improvedigital/params_test.go b/adapters/improvedigital/params_test.go index 13bdd807560..3767c0316a5 100644 --- a/adapters/improvedigital/params_test.go +++ b/adapters/improvedigital/params_test.go @@ -2,8 +2,9 @@ package improvedigital import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/infoawarebidder.go b/adapters/infoawarebidder.go index 321a87a3bec..3131a6404b1 100644 --- a/adapters/infoawarebidder.go +++ b/adapters/infoawarebidder.go @@ -4,16 +4,16 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // InfoAwareBidder wraps a Bidder to ensure all requests abide by the capabilities and // media types defined in the static/bidder-info/{bidder}.yaml file. // // It adjusts incoming requests in the following ways: -// 1. If App or Site traffic is not supported by the info file, then requests from +// 1. If App, Site or DOOH traffic is not supported by the info file, then requests from // those sources will be rejected before the delegate is called. // 2. If a given MediaType is not supported for the platform, then it will be set // to nil before the request is forwarded to the delegate. @@ -24,7 +24,7 @@ type InfoAwareBidder struct { info parsedBidderInfo } -// BuildInfoAwareBidder wraps a bidder to enforce site, app, and media type support. +// BuildInfoAwareBidder wraps a bidder to enforce inventory {site, app, dooh} and media type support. func BuildInfoAwareBidder(bidder Bidder, info config.BidderInfo) Bidder { return &InfoAwareBidder{ Bidder: bidder, @@ -47,6 +47,12 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *Ex } allowedMediaTypes = i.info.app } + if request.DOOH != nil { + if !i.info.dooh.enabled { + return nil, []error{&errortypes.Warning{Message: "this bidder does not support dooh requests"}} + } + allowedMediaTypes = i.info.dooh + } // Filtering imps is quite expensive (array filter with large, non-pointer elements)... but should be rare, // because it only happens if the publisher makes a really bad request. @@ -136,6 +142,7 @@ func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) type parsedBidderInfo struct { app parsedSupports site parsedSupports + dooh parsedSupports } type parsedSupports struct { @@ -148,13 +155,22 @@ type parsedSupports struct { func parseBidderInfo(info config.BidderInfo) parsedBidderInfo { var parsedInfo parsedBidderInfo - if info.Capabilities != nil && info.Capabilities.App != nil { + + if info.Capabilities == nil { + return parsedInfo + } + + if info.Capabilities.App != nil { parsedInfo.app.enabled = true parsedInfo.app.banner, parsedInfo.app.video, parsedInfo.app.audio, parsedInfo.app.native = parseAllowedTypes(info.Capabilities.App.MediaTypes) } - if info.Capabilities != nil && info.Capabilities.Site != nil { + if info.Capabilities.Site != nil { parsedInfo.site.enabled = true parsedInfo.site.banner, parsedInfo.site.video, parsedInfo.site.audio, parsedInfo.site.native = parseAllowedTypes(info.Capabilities.Site.MediaTypes) } + if info.Capabilities.DOOH != nil { + parsedInfo.dooh.enabled = true + parsedInfo.dooh.banner, parsedInfo.dooh.video, parsedInfo.dooh.audio, parsedInfo.dooh.native = parseAllowedTypes(info.Capabilities.DOOH.MediaTypes) + } return parsedInfo } diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index f42716bc1d6..38570e262d4 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -5,11 +5,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAppNotSupported(t *testing.T) { @@ -56,6 +57,26 @@ func TestSiteNotSupported(t *testing.T) { assert.Len(t, bids, 0) } +func TestDOOHNotSupported(t *testing.T) { + bidder := &mockBidder{} + info := config.BidderInfo{ + Capabilities: &config.CapabilitiesInfo{ + Site: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + } + constrained := adapters.BuildInfoAwareBidder(bidder, info) + bids, errs := constrained.MakeRequests(&openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "imp-1", Banner: &openrtb2.Banner{}}}, + DOOH: &openrtb2.DOOH{}, + }, &adapters.ExtraRequestInfo{}) + require.Len(t, errs, 1) + assert.EqualError(t, errs[0], "this bidder does not support dooh requests") + assert.IsType(t, &errortypes.Warning{}, errs[0]) + assert.Len(t, bids, 0) +} + func TestImpFiltering(t *testing.T) { bidder := &mockBidder{} info := config.BidderInfo{ @@ -66,6 +87,9 @@ func TestImpFiltering(t *testing.T) { App: &config.PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, }, + DOOH: &config.PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeNative}, + }, }, } @@ -153,10 +177,10 @@ func TestImpFiltering(t *testing.T) { description: "All imps with correct media type, MakeRequest() call expected", inBidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{ - {ID: "imp-1", Video: &openrtb2.Video{}}, - {ID: "imp-2", Video: &openrtb2.Video{}}, + {ID: "imp-1", Native: &openrtb2.Native{}}, + {ID: "imp-2", Native: &openrtb2.Native{}}, }, - Site: &openrtb2.Site{}, + DOOH: &openrtb2.DOOH{}, }, expectedErrors: nil, expectedImpLen: 2, diff --git a/adapters/infytv/infytv.go b/adapters/infytv/infytv.go index b403e710911..7e93a7296a5 100644 --- a/adapters/infytv/infytv.go +++ b/adapters/infytv/infytv.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/infytv/infytv_test.go b/adapters/infytv/infytv_test.go index 6834e99fa36..80527bbc4ae 100644 --- a/adapters/infytv/infytv_test.go +++ b/adapters/infytv/infytv_test.go @@ -3,9 +3,9 @@ package infytv import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/infytv/params_test.go b/adapters/infytv/params_test.go index 6719b102622..53230cbf263 100644 --- a/adapters/infytv/params_test.go +++ b/adapters/infytv/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index a5773c83be9..54d644cdc7d 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type InMobiAdapter struct { diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go index 0b6f5ffbd0e..40e77f5fbc3 100644 --- a/adapters/inmobi/inmobi_test.go +++ b/adapters/inmobi/inmobi_test.go @@ -3,9 +3,9 @@ package inmobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go index 2f90b707244..2e1a6417f14 100644 --- a/adapters/interactiveoffers/interactiveoffers.go +++ b/adapters/interactiveoffers/interactiveoffers.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go index 7805ae665bb..daf83869a95 100644 --- a/adapters/interactiveoffers/interactiveoffers_test.go +++ b/adapters/interactiveoffers/interactiveoffers_test.go @@ -3,9 +3,9 @@ package interactiveoffers import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/interactiveoffers/params_test.go b/adapters/interactiveoffers/params_test.go index 193c1b84839..8f14a51b512 100644 --- a/adapters/interactiveoffers/params_test.go +++ b/adapters/interactiveoffers/params_test.go @@ -2,8 +2,9 @@ package interactiveoffers import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/invibes/invibes.go b/adapters/invibes/invibes.go index b1272415d93..e1b978831b8 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -10,12 +10,12 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterVersion = "prebid_1.0.0" diff --git a/adapters/invibes/invibes_test.go b/adapters/invibes/invibes_test.go index 1ade5d276cd..e09837764f9 100644 --- a/adapters/invibes/invibes_test.go +++ b/adapters/invibes/invibes_test.go @@ -3,9 +3,9 @@ package invibes import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/invibes/params_test.go b/adapters/invibes/params_test.go index 8c3a26b4eac..88e08d1dc80 100644 --- a/adapters/invibes/params_test.go +++ b/adapters/invibes/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/iqx/iqx.go b/adapters/iqx/iqx.go new file mode 100644 index 00000000000..edfefa1895e --- /dev/null +++ b/adapters/iqx/iqx.go @@ -0,0 +1,164 @@ +package iqx + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type bidType struct { + Type string `json:"type"` +} + +type bidExt struct { + Prebid bidType `json:"prebid"` +} + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + tmpl, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint URL template: %v", err) + } + + bidder := &adapter{ + endpoint: tmpl, + } + + return bidder, nil +} + +func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { + var impExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), + } + } + + var iqzonexExt openrtb_ext.ExtIQX + if err := json.Unmarshal(impExt.Bidder, &iqzonexExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize IQZonex extension: %v", err), + } + } + + endpointParams := macros.EndpointTemplateParams{ + Host: iqzonexExt.Env, + SourceId: iqzonexExt.Pid, + } + + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requestCopy := *request + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + endpoint, err := a.buildEndpointFromRequest(&imp) + if err != nil { + errs = append(errs, err) + continue + } + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + request := &adapters.RequestData{ + Method: http.MethodPost, + Body: requestJSON, + Uri: endpoint, + Headers: headers, + } + + requests = append(requests, request) + } + + return requests, errs +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder IQZonex is unavailable. Please contact the bidder support.", + }} + } + + if err := adapters.CheckResponseStatusCodeForErrors(bidderRawResponse); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Array SeatBid cannot be empty", + }} + } + + return prepareBidResponse(bidResp.SeatBid) +} + +func prepareBidResponse(seats []openrtb2.SeatBid) (*adapters.BidderResponse, []error) { + errs := []error{} + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seats)) + + for _, seatBid := range seats { + for bidId, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[bidId], + BidType: bidType, + }) + } + } + + return bidResponse, errs +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("failed to parse bid mtype for impression id \"%s\"", bid.ImpID) + } +} diff --git a/adapters/iqx/iqxtest_test.go b/adapters/iqx/iqxtest_test.go new file mode 100644 index 00000000000..7385d37af91 --- /dev/null +++ b/adapters/iqx/iqxtest_test.go @@ -0,0 +1,27 @@ +package iqx + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderIQX, + config.Adapter{ + Endpoint: "http://rtb.iqzone.com/?pid={{.SourceId}}&host={{.Host}}&pbs=1", + }, + config.Server{ + ExternalUrl: "http://hosturl.com", + GvlID: 1, + DataCenter: "2", + }, + ) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "iqzonextest", bidder) +} diff --git a/adapters/iqx/iqzonextest/exemplary/banner.json b/adapters/iqx/iqzonextest/exemplary/banner.json new file mode 100644 index 00000000000..303de661a2d --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/banner.json @@ -0,0 +1,283 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + }, + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/exemplary/native.json b/adapters/iqx/iqzonextest/exemplary/native.json new file mode 100644 index 00000000000..f458d70e127 --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/native.json @@ -0,0 +1,164 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/exemplary/video.json b/adapters/iqx/iqzonextest/exemplary/video.json new file mode 100644 index 00000000000..21826ac0440 --- /dev/null +++ b/adapters/iqx/iqzonextest/exemplary/video.json @@ -0,0 +1,204 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/bad-response.json b/adapters/iqx/iqzonextest/supplemental/bad-response.json new file mode 100644 index 00000000000..3d9ff9ffcd3 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/bad-response.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json b/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json new file mode 100644 index 00000000000..3f471c9c06e --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-mediatype.json @@ -0,0 +1,190 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 1, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "some": "value" + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "failed to parse bid mtype for impression id \"2\"", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json b/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json new file mode 100644 index 00000000000..66b78f4bfbd --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-seatbid-0-bid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [], + "expectedBidResponses": [{"currency":"USD","bids":[]}] +} diff --git a/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json b/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..4df7e16f767 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/empty-seatbid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Array SeatBid cannot be empty", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json b/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json new file mode 100644 index 00000000000..2f124a8cf3b --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-ext-bidder-object.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": [] + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize IQZonex extension: json: cannot unmarshal array into Go value of type openrtb_ext.ExtIQX", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json b/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json new file mode 100644 index 00000000000..aa215eb3e34 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-ext-object.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": "" + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize bidder impression extension: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json b/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json new file mode 100644 index 00000000000..f2ded0f0cb4 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/invalid-mediatype.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250 + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "failed to parse bid mtype for impression id \"2\"", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-204.json b/adapters/iqx/iqzonextest/supplemental/status-204.json new file mode 100644 index 00000000000..c850d34b527 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-204.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-400.json b/adapters/iqx/iqzonextest/supplemental/status-400.json new file mode 100644 index 00000000000..7e3d17d3baf --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-400.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/status-503.json b/adapters/iqx/iqzonextest/supplemental/status-503.json new file mode 100644 index 00000000000..1b6dd02af8c --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/status-503.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bidder IQZonex is unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/iqzonextest/supplemental/unexpected-status.json b/adapters/iqx/iqzonextest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..edfdf5fdd80 --- /dev/null +++ b/adapters/iqx/iqzonextest/supplemental/unexpected-status.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.iqzone.com/?pid=3163e2c9e034770c0daaa98c7613b573&host=iqzonex-stage&pbs=1", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "iqzonex-stage", + "pid": "3163e2c9e034770c0daaa98c7613b573" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Access is denied" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/iqx/params_test.go b/adapters/iqx/params_test.go new file mode 100644 index 00000000000..23c4d007acb --- /dev/null +++ b/adapters/iqx/params_test.go @@ -0,0 +1,53 @@ +package iqx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var validParams = []string{ + `{"env":"iqzonex-stage", "pid":"123456"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderIQX, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected iqzonex params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"some": "param"}`, + `{"env":"iqzonex-stage"}`, + `{"pid":"1234"}`, + `{"othervalue":"Lorem ipsum"}`, + `{"env":"iqzonex-stage", pid:""}`, + `{"env":"", pid:"1234"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderIQX, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/iqzone/iqzone.go b/adapters/iqzone/iqzone.go index 9c36a021e21..ce095c97009 100644 --- a/adapters/iqzone/iqzone.go +++ b/adapters/iqzone/iqzone.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/iqzone/iqzone_test.go b/adapters/iqzone/iqzone_test.go index 3e1afbe1909..c5713953f40 100644 --- a/adapters/iqzone/iqzone_test.go +++ b/adapters/iqzone/iqzone_test.go @@ -3,9 +3,9 @@ package iqzone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/iqzone/params_test.go b/adapters/iqzone/params_test.go index c06f5e7fab4..2c6bb223845 100644 --- a/adapters/iqzone/params_test.go +++ b/adapters/iqzone/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index b17d913f42d..35b3f7f2711 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -7,11 +7,11 @@ import ( "sort" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" "github.com/prebid/openrtb/v19/native1" native1response "github.com/prebid/openrtb/v19/native1/response" @@ -19,8 +19,7 @@ import ( ) type IxAdapter struct { - URI string - maxRequests int + URI string } type ExtRequest struct { @@ -30,108 +29,151 @@ type ExtRequest struct { } type IxDiag struct { - PbsV string `json:"pbsv,omitempty"` - PbjsV string `json:"pbjsv,omitempty"` + PbsV string `json:"pbsv,omitempty"` + PbjsV string `json:"pbjsv,omitempty"` + MultipleSiteIds string `json:"multipleSiteIds,omitempty"` } -func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - nImp := len(request.Imp) - if nImp > a.maxRequests { - request.Imp = request.Imp[:a.maxRequests] - nImp = a.maxRequests - } - - errs := make([]error, 0) +type auctionConfig struct { + BidId string `json:"bidId,omitempty"` + Config json.RawMessage `json:"config,omitempty"` +} - if err := BuildIxDiag(request); err != nil { - errs = append(errs, err) - } +type ixRespExt struct { + AuctionConfig []auctionConfig `json:"protectedAudienceAuctionConfigs,omitempty"` +} - // Multi-size banner imps are split into single-size requests. - // The first size imp requests are added to the first slice. - // Additional size requests are added to the second slice and are merged with the first at the end. - // Preallocate the max possible size to avoid reallocating arrays. - requests := make([]*adapters.RequestData, 0, a.maxRequests) - multiSizeRequests := make([]*adapters.RequestData, 0, a.maxRequests-nImp) +func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requests := make([]*adapters.RequestData, 0, len(request.Imp)) + errs := make([]error, 0) headers := http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}} - imps := request.Imp - for iImp := range imps { - request.Imp = imps[iImp : iImp+1] - if request.Site != nil { - if err := setSitePublisherId(request, iImp); err != nil { - errs = append(errs, err) - continue - } + uniqueSiteIDs := make(map[string]struct{}) + filteredImps := make([]openrtb2.Imp, 0, len(request.Imp)) + requestCopy := *request + + ixDiag := &IxDiag{} + + for _, imp := range requestCopy.Imp { + var err error + ixExt, err := unmarshalToIxExt(&imp) + + if err != nil { + errs = append(errs, err) + continue } - if request.Imp[0].Banner != nil { - banner := *request.Imp[0].Banner - request.Imp[0].Banner = &banner - formats := getBannerFormats(&banner) - for iFmt := range formats { - banner.Format = formats[iFmt : iFmt+1] - banner.W = openrtb2.Int64Ptr(banner.Format[0].W) - banner.H = openrtb2.Int64Ptr(banner.Format[0].H) - if requestData, err := createRequestData(a, request, &headers); err == nil { - if iFmt == 0 { - requests = append(requests, requestData) - } else { - multiSizeRequests = append(multiSizeRequests, requestData) - } - } else { - errs = append(errs, err) - } - if len(multiSizeRequests) == cap(multiSizeRequests) { - break - } + if err = parseSiteId(ixExt, uniqueSiteIDs); err != nil { + errs = append(errs, err) + continue + } + + if err := moveSid(&imp, ixExt); err != nil { + errs = append(errs, err) + } + + if imp.Banner != nil { + bannerCopy := *imp.Banner + + if len(bannerCopy.Format) == 0 && bannerCopy.W != nil && bannerCopy.H != nil { + bannerCopy.Format = []openrtb2.Format{{W: *bannerCopy.W, H: *bannerCopy.H}} + } + + if len(bannerCopy.Format) == 1 { + bannerCopy.W = openrtb2.Int64Ptr(bannerCopy.Format[0].W) + bannerCopy.H = openrtb2.Int64Ptr(bannerCopy.Format[0].H) } - } else if requestData, err := createRequestData(a, request, &headers); err == nil { + imp.Banner = &bannerCopy + } + filteredImps = append(filteredImps, imp) + } + requestCopy.Imp = filteredImps + + setPublisherId(&requestCopy, uniqueSiteIDs, ixDiag) + + err := setIxDiagIntoExtRequest(&requestCopy, ixDiag) + if err != nil { + errs = append(errs, err) + } + + if len(requestCopy.Imp) != 0 { + if requestData, err := createRequestData(a, &requestCopy, &headers); err == nil { requests = append(requests, requestData) } else { errs = append(errs, err) } } - request.Imp = imps - return append(requests, multiSizeRequests...), errs + return requests, errs } -func setSitePublisherId(request *openrtb2.BidRequest, iImp int) error { - if iImp == 0 { - // first impression - create a site and pub copy - site := *request.Site +func setPublisherId(requestCopy *openrtb2.BidRequest, uniqueSiteIDs map[string]struct{}, ixDiag *IxDiag) { + siteIDs := make([]string, 0, len(uniqueSiteIDs)) + for key := range uniqueSiteIDs { + siteIDs = append(siteIDs, key) + } + if requestCopy.Site != nil { + site := *requestCopy.Site if site.Publisher == nil { site.Publisher = &openrtb2.Publisher{} } else { publisher := *site.Publisher site.Publisher = &publisher } - request.Site = &site + if len(siteIDs) == 1 { + site.Publisher.ID = siteIDs[0] + } + requestCopy.Site = &site + } + + if requestCopy.App != nil { + app := *requestCopy.App + + if app.Publisher == nil { + app.Publisher = &openrtb2.Publisher{} + } else { + publisher := *app.Publisher + app.Publisher = &publisher + } + if len(siteIDs) == 1 { + app.Publisher.ID = siteIDs[0] + } + requestCopy.App = &app } + if len(siteIDs) > 1 { + // Sorting siteIDs for predictable output as Go maps don't guarantee order + sort.Strings(siteIDs) + multipleSiteIDs := strings.Join(siteIDs, ", ") + ixDiag.MultipleSiteIds = multipleSiteIDs + } +} + +func unmarshalToIxExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpIx, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { - return err + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, err } var ixExt openrtb_ext.ExtImpIx if err := json.Unmarshal(bidderExt.Bidder, &ixExt); err != nil { - return err + return nil, err } - request.Site.Publisher.ID = ixExt.SiteId - return nil + return &ixExt, nil } -func getBannerFormats(banner *openrtb2.Banner) []openrtb2.Format { - if len(banner.Format) == 0 && banner.W != nil && banner.H != nil { - banner.Format = []openrtb2.Format{{W: *banner.W, H: *banner.H}} +func parseSiteId(ixExt *openrtb_ext.ExtImpIx, uniqueSiteIDs map[string]struct{}) error { + if ixExt == nil { + return fmt.Errorf("Nil Ix Ext") } - return banner.Format + if ixExt.SiteId != "" { + uniqueSiteIDs[ixExt.SiteId] = struct{}{} + } + return nil } func createRequestData(a *IxAdapter, request *openrtb2.BidRequest, headers *http.Header) (*adapters.RequestData, error) { @@ -180,7 +222,8 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque } } - bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5) + // capacity 0 will make channel unbuffered + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(0) bidderResponse.Currency = bidResponse.Cur var errs []error @@ -239,6 +282,26 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque } } + if bidResponse.Ext != nil { + var bidRespExt ixRespExt + if err := json.Unmarshal(bidResponse.Ext, &bidRespExt); err != nil { + return nil, append(errs, err) + } + + if bidRespExt.AuctionConfig != nil { + bidderResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.AuctionConfig)) + for _, config := range bidRespExt.AuctionConfig { + if config.Config != nil { + fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{ + ImpId: config.BidId, + Config: config.Config, + } + bidderResponse.FledgeAuctionConfigs = append(bidderResponse.FledgeAuctionConfigs, fledgeAuctionConfig) + } + } + } + } + return bidderResponse, errs } @@ -275,8 +338,7 @@ func getMediaTypeForBid(bid openrtb2.Bid, impMediaTypeReq map[string]openrtb_ext // Builder builds a new instance of the Ix adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &IxAdapter{ - URI: config.Endpoint, - maxRequests: 20, + URI: config.Endpoint, } return bidder, nil } @@ -328,29 +390,33 @@ func marshalJsonWithoutUnicode(v interface{}) (string, error) { return strings.TrimSuffix(sb.String(), "\n"), nil } -func BuildIxDiag(request *openrtb2.BidRequest) error { +func setIxDiagIntoExtRequest(request *openrtb2.BidRequest, ixDiag *IxDiag) error { extRequest := &ExtRequest{} if request.Ext != nil { if err := json.Unmarshal(request.Ext, &extRequest); err != nil { return err } } - ixdiag := &IxDiag{} if extRequest.Prebid != nil && extRequest.Prebid.Channel != nil { - ixdiag.PbjsV = extRequest.Prebid.Channel.Version + ixDiag.PbjsV = extRequest.Prebid.Channel.Version } - // Slice commit hash out of version if strings.Contains(version.Ver, "-") { - ixdiag.PbsV = version.Ver[:strings.Index(version.Ver, "-")] + ixDiag.PbsV = version.Ver[:strings.Index(version.Ver, "-")] } else if version.Ver != "" { - ixdiag.PbsV = version.Ver + ixDiag.PbsV = version.Ver } // Only set request.ext if ixDiag is not empty - if *ixdiag != (IxDiag{}) { - extRequest.IxDiag = ixdiag + if *ixDiag != (IxDiag{}) { + extRequest := &ExtRequest{} + if request.Ext != nil { + if err := json.Unmarshal(request.Ext, &extRequest); err != nil { + return err + } + } + extRequest.IxDiag = ixDiag extRequestJson, err := json.Marshal(extRequest) if err != nil { return err @@ -359,3 +425,24 @@ func BuildIxDiag(request *openrtb2.BidRequest) error { } return nil } + +// moves sid from imp[].ext.bidder.sid to imp[].ext.sid +func moveSid(imp *openrtb2.Imp, ixExt *openrtb_ext.ExtImpIx) error { + if ixExt == nil { + return fmt.Errorf("Nil Ix Ext") + } + + if ixExt.Sid != "" { + var m map[string]interface{} + if err := json.Unmarshal(imp.Ext, &m); err != nil { + return err + } + m["sid"] = ixExt.Sid + ext, err := json.Marshal(m) + if err != nil { + return err + } + imp.Ext = ext + } + return nil +} diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index 0f6b856dce4..5e2450e7ded 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" "github.com/stretchr/testify/assert" "github.com/prebid/openrtb/v19/adcom1" @@ -19,8 +19,6 @@ const endpoint string = "http://host/endpoint" func TestJsonSamples(t *testing.T) { if bidder, err := Builder(openrtb_ext.BidderIx, config.Adapter{Endpoint: endpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}); err == nil { - ixBidder := bidder.(*IxAdapter) - ixBidder.maxRequests = 2 adapterstest.RunJSONBidderTest(t, "ixtest", bidder) } else { t.Fatalf("Builder returned unexpected error %v", err) @@ -44,7 +42,7 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) { `{ "prebid": {}, "bidder": { - "siteID": 123456 + "siteID": "123456" } }`, )}, @@ -106,7 +104,6 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) { func TestIxMakeRequestWithGppString(t *testing.T) { bidder := &IxAdapter{} - bidder.maxRequests = 2 testGppString := "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN" @@ -124,7 +121,7 @@ func TestIxMakeRequestWithGppString(t *testing.T) { `{ "prebid": {}, "bidder": { - "siteID": 123456 + "siteId": "123456" } }`, )}, @@ -256,7 +253,8 @@ func TestBuildIxDiag(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { version.Ver = test.pbsVersion - err := BuildIxDiag(test.request) + ixDiag := &IxDiag{} + err := setIxDiagIntoExtRequest(test.request, ixDiag) if test.expectError { assert.NotNil(t, err) } else { @@ -276,3 +274,109 @@ func TestMakeRequestsErrIxDiag(t *testing.T) { _, errs := bidder.MakeRequests(req, nil) assert.Len(t, errs, 1) } + +func TestPABidResponse(t *testing.T) { + bidder := &IxAdapter{} + + mockedReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "1_1", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{W: 300, H: 250}}, + }, + Ext: json.RawMessage( + `{ + "ae": 1, + "bidder": { + "siteID": "123456" + } + }`, + )}, + }, + } + mockedExtReq := &adapters.RequestData{} + mockedBidResponse := &openrtb2.BidResponse{ + ID: "test-1", + SeatBid: []openrtb2.SeatBid{{ + Seat: "Buyer", + Bid: []openrtb2.Bid{{ + ID: "1", + ImpID: "1_1", + Price: 1.23, + AdID: "123", + Ext: json.RawMessage( + `{ + "prebid": { + "video": { + "duration": 60, + "primary_category": "IAB18-1" + } + } + }`, + ), + }}, + }}, + } + + testCases := []struct { + name string + ext json.RawMessage + expectedLen int + }{ + { + name: "properly formatted", + ext: json.RawMessage( + `{ + "protectedAudienceAuctionConfigs": [{ + "bidId": "test-imp-id", + "config": { + "seller": "https://seller.com", + "decisionLogicUrl": "https://ssp.com/decision-logic.js", + "interestGroupBuyers": [ + "https://buyer.com" + ], + "sellerSignals": { + "callbackUrl": "https://callbackurl.com" + }, + "perBuyerSignals": { + "https://buyer.com": [] + } + } + }] + }`, + ), + expectedLen: 1, + }, + { + name: "no protected audience auction configs returned", + ext: json.RawMessage(`{}`), + expectedLen: 0, + }, + { + name: "no config", + ext: json.RawMessage( + `{ + "protectedAudienceAuctionConfigs": [{ + "bidId": "test-imp-id" + }] + }`, + ), + expectedLen: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockedBidResponse.Ext = tc.ext + body, _ := json.Marshal(mockedBidResponse) + mockedRes := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes) + + assert.Nil(t, errors) + assert.Len(t, bidResponse.FledgeAuctionConfigs, tc.expectedLen) + }) + } +} diff --git a/adapters/ix/ixtest/exemplary/app-site-id.json b/adapters/ix/ixtest/exemplary/app-site-id.json new file mode 100644 index 00000000000..80441cc4382 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/app-site-id.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "app": { + "domain": "https://www.example.com/" + }, + "ext": { + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "app": { + "domain": "https://www.example.com/", + "publisher": { + "id": "569749" + } + }, + "ext": { + "ixdiag": { + "pbjsv": "7.0.0" + }, + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/exemplary/fledge.json b/adapters/ix/ixtest/exemplary/fledge.json new file mode 100644 index 00000000000..919c7076556 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/fledge.json @@ -0,0 +1,171 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + "id": "569749" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD", + "ext": { + "protectedAudienceAuctionConfigs": [{ + "bidId": "test-imp-id", + "config": { + "seller": "https://seller.com", + "decisionLogicUrl": "https://ssp.com/decision-logic.js", + "interestGroupBuyers": [ + "https://buyer.com" + ], + "sellerSignals": { + "callbackURL": "https://callbackurl.com" + }, + "perBuyerSignals": { + "https://buyer.com": [] + } + } + }] + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ], + "fledgeauctionconfigs": [{ + "impid": "test-imp-id", + "config": { + "seller": "https://seller.com", + "decisionLogicUrl": "https://ssp.com/decision-logic.js", + "interestGroupBuyers": [ + "https://buyer.com" + ], + "sellerSignals": { + "callbackURL": "https://callbackurl.com" + }, + "perBuyerSignals": { + "https://buyer.com": [] + } + } + }] + } + ] +} diff --git a/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json b/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json new file mode 100644 index 00000000000..4decdaf985d --- /dev/null +++ b/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json @@ -0,0 +1,211 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "site": { + "page": "https://www.example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + "id": "569749" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 600, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 600, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/exemplary/max-requests.json b/adapters/ix/ixtest/exemplary/multi-imp-requests.json similarity index 69% rename from adapters/ix/ixtest/exemplary/max-requests.json rename to adapters/ix/ixtest/exemplary/multi-imp-requests.json index 46d9ec5d6b7..d8d00aeea69 100644 --- a/adapters/ix/ixtest/exemplary/max-requests.json +++ b/adapters/ix/ixtest/exemplary/multi-imp-requests.json @@ -9,10 +9,6 @@ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, @@ -62,6 +58,38 @@ "siteId": "569749" } } + }, + { + "id": "test-imp-id-4", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-5", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } } ] }, @@ -89,49 +117,7 @@ "siteId": "569749" } } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-id-1", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "https://advertiser.example.com" - ], - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300, - "ext": { - "ix": {} - } - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - }, - { - "expectedRequest": { - "uri": "http://host/endpoint", - "body": { - "id": "test-request-id", - "imp": [ + }, { "id": "test-imp-id-2", "video": { @@ -156,6 +142,60 @@ "siteId": "569749" } } + }, + { + "banner": { + "format": [ + { + "h": 600, + "w": 300 + } + ], + "h": 600, + "w": 300 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + }, + "id": "test-imp-id-3" + }, + { + "id": "test-imp-id-4", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-5", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } } ] } @@ -170,7 +210,7 @@ "bid": [ { "id": "7706636740145184841", - "impid": "test-imp-id-2", + "impid": "test-imp-id-1", "price": 0.5, "adid": "29681110", "adm": "some-test-ad", @@ -181,9 +221,6 @@ "crid": "29681110", "h": 250, "w": 300, - "cat": [ - "IAB9-1" - ], "ext": { "ix": {} } @@ -222,34 +259,6 @@ "type": "banner" } ] - }, - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-id-2", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "https://advertiser.example.com" - ], - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 250, - "cat": [ - "IAB9-1" - ], - "ext": { - "ix": {} - } - }, - "type": "video" - } - ] } ] } diff --git a/adapters/ix/ixtest/exemplary/multiple-siteIds.json b/adapters/ix/ixtest/exemplary/multiple-siteIds.json new file mode 100644 index 00000000000..7f4227ac4b2 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/multiple-siteIds.json @@ -0,0 +1,224 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569750" + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569751" + } + } + } + ], + "site": { + "page": "https://www.example.com/" + }, + "ext": { + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569750" + } + } + }, + { + "banner": { + "format": [ + { + "h": 600, + "w": 300 + } + ], + "h": 600, + "w": 300 + }, + "ext": { + "bidder": { + "siteId": "569751" + } + }, + "id": "test-imp-id-3" + } + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + } + }, + "ext": { + "ixdiag": { + "multipleSiteIds": "569749, 569750, 569751", + "pbjsv": "7.0.0" + }, + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json b/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json index 5e0a311a91b..15dc3ecbb0c 100644 --- a/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json +++ b/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json @@ -13,6 +13,10 @@ { "w": 300, "h": 600 + }, + { + "w": 600, + "h": 800 } ] }, @@ -22,7 +26,10 @@ } } } - ] + ], + "site": { + "page": "https://www.example.com/" + } }, "httpCalls": [ { @@ -38,70 +45,16 @@ { "w": 300, "h": 250 - } - ], - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "siteId": "569749" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "https://advertiser.example.com" - ], - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300, - "ext": { - "ix": {} - } - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - }, - { - "expectedRequest": { - "uri": "http://host/endpoint", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + }, { "w": 300, "h": 600 + }, + { + "w": 600, + "h": 800 } - ], - "w": 300, - "h": 600 + ] }, "ext": { "bidder": { @@ -109,7 +62,13 @@ } } } - ] + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + "id": "569749" + } + } } }, "mockResponse": { @@ -131,7 +90,7 @@ ], "cid": "958", "crid": "29681110", - "h": 600, + "h": 250, "w": 300, "ext": { "ix": {} @@ -171,31 +130,6 @@ "type": "banner" } ] - }, - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "https://advertiser.example.com" - ], - "cid": "958", - "crid": "29681110", - "w": 300, - "h": 600, - "ext": { - "ix": {} - } - }, - "type": "banner" - } - ] } ] } diff --git a/adapters/ix/ixtest/exemplary/structured-pod.json b/adapters/ix/ixtest/exemplary/structured-pod.json new file mode 100644 index 00000000000..a5ca9895554 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/structured-pod.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560, + "podid": "1", + "slotinpod": 1, + "podseq": 1 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560, + "podid": "1", + "slotinpod": -1, + "podseq": 1 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560, + "podid": "1", + "slotinpod": 1, + "podseq": 1 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560, + "podid": "1", + "slotinpod": -1, + "podseq": 1 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 560, + "w": 940, + "dur": 30, + "ext": { + "ix": {} + } + } + ] + }, + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-2", + "price": 0.75, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 560, + "w": 940, + "dur": 30, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 560, + "w": 940, + "dur": 30, + "ext": { + "ix": {} + } + }, + "type": "video" + }, + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-2", + "price": 0.75, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 560, + "w": 940, + "dur": 30, + "ext": { + "ix": {} + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/app-site-id-publisher.json b/adapters/ix/ixtest/supplemental/app-site-id-publisher.json new file mode 100644 index 00000000000..aec2c124ca9 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/app-site-id-publisher.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "app": { + "domain": "https://www.example.com/", + "publisher": { + "name": "https://www.example.com/" + } + }, + "ext": { + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ], + "app": { + "domain": "https://www.example.com/", + "publisher": { + "id": "569749", + "name": "https://www.example.com/" + } + }, + "ext": { + "ixdiag": { + "pbjsv": "7.0.0" + }, + "prebid": { + "channel": { + "name": "web", + "version": "7.0.0" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/bad-fledge.json b/adapters/ix/ixtest/supplemental/bad-fledge.json new file mode 100644 index 00000000000..97cc91e4ae6 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/bad-fledge.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + "id": "569749" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD", + "ext": { + "protectedAudienceAuctionConfigs": [{ + "bidId": 123, + "config": { + "seller": "https://seller.com", + "decisionLogicUrl": "https://ssp.com/decision-logic.js", + "interestGroupBuyers": [ + "https://buyer.com" + ], + "sellerSignals": { + "callbackURL": "https://callbackurl.com" + }, + "perBuyerSignals": { + "https://buyer.com": [] + } + } + }] + } + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field auctionConfig.protectedAudienceAuctionConfigs.bidId of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/fledge-no-bid.json b/adapters/ix/ixtest/supplemental/fledge-no-bid.json new file mode 100644 index 00000000000..139dd579ae7 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/fledge-no-bid.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 600, + "h": 800 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + }, + "ae": 1 + } + } + ], + "site": { + "page": "https://www.example.com/", + "publisher": { + "id": "569749" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json b/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json new file mode 100644 index 00000000000..223e863284c --- /dev/null +++ b/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json @@ -0,0 +1,189 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749", + "sid": 12345 + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 600, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 600, + "ext": { + "ix": {} + } + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field ExtImpIx.sid of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/sid.json b/adapters/ix/ixtest/supplemental/sid.json new file mode 100644 index 00000000000..3d6862d1235 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/sid.json @@ -0,0 +1,190 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749", + "sid": "1234" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569750", + "sid": "5678" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "ext": { + "ixdiag": { + "multipleSiteIds": "569749, 569750" + }, + "prebid": null + }, + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569749", + "sid": "1234" + }, + "sid": "1234" + } + }, + { + "id": "test-imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "siteId": "569750", + "sid": "5678" + }, + "sid": "5678" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id-1", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go index 9246a43a725..ba426c0b27b 100644 --- a/adapters/ix/params_test.go +++ b/adapters/ix/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { @@ -38,6 +38,7 @@ var validParams = []string{ `{"siteID":"12345"}`, `{"siteId":"123456"}`, `{"siteid":"1234567", "size": [640,480]}`, + `{"siteId":"123456", "sid":"12345"}`, } var invalidParams = []string{ diff --git a/adapters/jixie/jixie.go b/adapters/jixie/jixie.go index 6018b73709b..fa87bd5da03 100644 --- a/adapters/jixie/jixie.go +++ b/adapters/jixie/jixie.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/jixie/jixie_test.go b/adapters/jixie/jixie_test.go index 56260a6c04d..dff2b3574d3 100644 --- a/adapters/jixie/jixie_test.go +++ b/adapters/jixie/jixie_test.go @@ -3,9 +3,9 @@ package jixie import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/jixie/params_test.go b/adapters/jixie/params_test.go index 4bc2e3080c1..cbcdbe959de 100644 --- a/adapters/jixie/params_test.go +++ b/adapters/jixie/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kargo/kargo.go b/adapters/kargo/kargo.go index e10bbbe466b..003275c51d3 100644 --- a/adapters/kargo/kargo.go +++ b/adapters/kargo/kargo.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kargo/kargo_test.go b/adapters/kargo/kargo_test.go index 482ef2361cf..f2ceb46f643 100644 --- a/adapters/kargo/kargo_test.go +++ b/adapters/kargo/kargo_test.go @@ -3,9 +3,9 @@ package kargo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/kargo/params_test.go b/adapters/kargo/params_test.go index cb9ffaf1028..9907d67116c 100644 --- a/adapters/kargo/params_test.go +++ b/adapters/kargo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go index 3dac0a50e86..a4c459ac2a2 100644 --- a/adapters/kayzen/kayzen.go +++ b/adapters/kayzen/kayzen.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kayzen/kayzen_test.go b/adapters/kayzen/kayzen_test.go index dc6a7db4735..a2cc98a7798 100644 --- a/adapters/kayzen/kayzen_test.go +++ b/adapters/kayzen/kayzen_test.go @@ -3,9 +3,9 @@ package kayzen import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/kayzen/params_test.go b/adapters/kayzen/params_test.go index 07bd0851a97..bd03e23df5b 100644 --- a/adapters/kayzen/params_test.go +++ b/adapters/kayzen/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index b2637768bb1..f18ed98c07b 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type KidozAdapter struct { diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index ffde298ee0e..0a5e21fd62d 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/kidoz/params_test.go b/adapters/kidoz/params_test.go index 073d7382d68..fad4e681288 100644 --- a/adapters/kidoz/params_test.go +++ b/adapters/kidoz/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/kiviads/kiviads.go b/adapters/kiviads/kiviads.go index 88d245a5db2..8bdf72145d4 100644 --- a/adapters/kiviads/kiviads.go +++ b/adapters/kiviads/kiviads.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/kiviads/kiviads_test.go b/adapters/kiviads/kiviads_test.go index 41e9bcbcdee..a6725234e6a 100644 --- a/adapters/kiviads/kiviads_test.go +++ b/adapters/kiviads/kiviads_test.go @@ -3,9 +3,9 @@ package kiviads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/kiviads/params_test.go b/adapters/kiviads/params_test.go index 177e2f149b3..09ee3e24265 100644 --- a/adapters/kiviads/params_test.go +++ b/adapters/kiviads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index 71a869a42a1..e809960db8c 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type KrushmediaAdapter struct { diff --git a/adapters/krushmedia/krushmedia_test.go b/adapters/krushmedia/krushmedia_test.go index 7ffbc0a9361..e7d71422dfe 100644 --- a/adapters/krushmedia/krushmedia_test.go +++ b/adapters/krushmedia/krushmedia_test.go @@ -3,9 +3,9 @@ package krushmedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/krushmedia/params_test.go b/adapters/krushmedia/params_test.go index 26daa56e10b..4129fa1bd29 100644 --- a/adapters/krushmedia/params_test.go +++ b/adapters/krushmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go deleted file mode 100644 index 8a3a7baa65a..00000000000 --- a/adapters/kubient/kubient.go +++ /dev/null @@ -1,149 +0,0 @@ -package kubient - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" -) - -// Builder builds a new instance of the Kubient adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &KubientAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -// Implements Bidder interface. -type KubientAdapter struct { - endpoint string -} - -// MakeRequests prepares the HTTP requests which should be made to fetch bids. -func (adapter *KubientAdapter) MakeRequests( - openRTBRequest *openrtb2.BidRequest, - reqInfo *adapters.ExtraRequestInfo, -) ([]*adapters.RequestData, []error) { - if len(openRTBRequest.Imp) == 0 { - return nil, []error{&errortypes.BadInput{ - Message: "No impression in the bid request", - }} - } - errs := make([]error, 0, len(openRTBRequest.Imp)) - hasErrors := false - for _, impObj := range openRTBRequest.Imp { - err := checkImpExt(impObj) - if err != nil { - errs = append(errs, err) - hasErrors = true - } - } - if hasErrors { - return nil, errs - } - openRTBRequestJSON, err := json.Marshal(openRTBRequest) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - requestsToBidder := []*adapters.RequestData{{ - Method: "POST", - Uri: adapter.endpoint, - Body: openRTBRequestJSON, - Headers: headers, - }} - return requestsToBidder, errs -} - -func checkImpExt(impObj openrtb2.Imp) error { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(impObj.Ext, &bidderExt); err != nil { - return &errortypes.BadInput{ - Message: "ext.bidder not provided", - } - } - var kubientExt openrtb_ext.ExtImpKubient - if err := json.Unmarshal(bidderExt.Bidder, &kubientExt); err != nil { - return &errortypes.BadInput{ - Message: "ext.bidder.zoneid is not provided", - } - } - if kubientExt.ZoneID == "" { - return &errortypes.BadInput{ - Message: "zoneid is empty", - } - } - return nil -} - -// MakeBids makes the bids -func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var errs []error - - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - var bidResp openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) - if err != nil { - errs = append(errs, err) - } else { - b := &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: bidType, - } - bidResponse.Bids = append(bidResponse.Bids, b) - } - } - } - return bidResponse, errs -} - -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner == nil && imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } - return mediaType, nil - } - } - - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), - } -} diff --git a/adapters/kubient/kubient_test.go b/adapters/kubient/kubient_test.go deleted file mode 100644 index 292bb20641a..00000000000 --- a/adapters/kubient/kubient_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package kubient - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderKubient, config.Adapter{ - Endpoint: "http://127.0.0.1:5000/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "kubienttest", bidder) -} diff --git a/adapters/kubient/kubienttest/exemplary/banner.json b/adapters/kubient/kubienttest/exemplary/banner.json deleted file mode 100644 index abcfc05c041..00000000000 --- a/adapters/kubient/kubienttest/exemplary/banner.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-banner-request-id", - "imp": [ - { - "id": "test-imp-banner-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "9042" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-banner-request-id", - "imp": [ - { - "id": "test-imp-banner-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "zoneid": "9042" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-banner-request-id", - "seatbid": [ - { - "seat": "772", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-banner-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["advertsite.com"], - "cid": "772", - "crid": "29681110", - "h": 576, - "w": 1024 - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-banner-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["advertsite.com"], - "cid": "772", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/kubient/kubienttest/exemplary/video.json b/adapters/kubient/kubienttest/exemplary/video.json deleted file mode 100644 index 69de5935f48..00000000000 --- a/adapters/kubient/kubienttest/exemplary/video.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-video-request-id", - "imp": [ - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "zoneid": "9010" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", - "body": { - "id": "test-video-request-id", - "imp": [ - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "zoneid": "9010" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "ssp-response-id", - "seatbid": [ - { - "seat": "83", - "bid": [{ - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-video-ad", - "adomain": ["advertsite.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-video-ad", - "adid": "29681110", - "adomain": ["advertsite.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] - -} diff --git a/adapters/lemmadigital/lemmadigital.go b/adapters/lemmadigital/lemmadigital.go new file mode 100644 index 00000000000..d09793fa4e8 --- /dev/null +++ b/adapters/lemmadigital/lemmadigital.go @@ -0,0 +1,114 @@ +package lemmadigital + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} + +// Builder builds a new instance of the Lemmadigital adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{errors.New("Impression array should not be empty")} + } + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + + var impExt openrtb_ext.ImpExtLemmaDigital + if err := json.Unmarshal(bidderExt.Bidder, &impExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext.bidder for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + + endpoint, err := a.buildEndpointURL(impExt) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidType := openrtb_ext.BidTypeBanner + if nil != request.Imp[0].Video { + bidType = openrtb_ext.BidTypeVideo + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) > 0 { + bidResponse.Currency = response.Cur + } + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + break + } + + return bidResponse, nil +} + +func (a *adapter) buildEndpointURL(params openrtb_ext.ImpExtLemmaDigital) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: strconv.Itoa(params.PublisherId), + AdUnit: strconv.Itoa(params.AdId)} + return macros.ResolveMacros(a.endpoint, endpointParams) +} diff --git a/adapters/lemmadigital/lemmadigital_test.go b/adapters/lemmadigital/lemmadigital_test.go new file mode 100644 index 00000000000..e0062c0b565 --- /dev/null +++ b/adapters/lemmadigital/lemmadigital_test.go @@ -0,0 +1,20 @@ +package lemmadigital + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderLemmadigital, config.Adapter{ + Endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "lemmadigitaltest", bidder) +} diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json new file mode 100644 index 00000000000..a478380e394 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "h": 1080, + "w": 1920, + "dealid": "test_deal" + }] + }], + "bidid": "1239875642389471056", + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.5, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "w": 1920, + "h": 1080, + "dealid": "test_deal" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json new file mode 100644 index 00000000000..e051b54ff95 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "h": 1080, + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }, { + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 1080, + "h": 1920 + }], + "h": 1920, + "w": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.12 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 1920, + "h": 1080 + }], + "h": 1080, + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.1 + }, { + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 1080, + "h": 1920 + }], + "h": 1920, + "w": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "bidfloor": 0.12 + }], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "h": 1080, + "w": 1920, + "dealid": "test_deal" + }] + }], + "bidid": "1239875642389471056", + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id", + "price": 0.5, + "adid": "1", + "adm": "some-test-ad", + "adomain": ["lemmadigital.com"], + "crid": "1", + "w": 1920, + "h": 1080, + "dealid": "test_deal" + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json new file mode 100644 index 00000000000..63bab75b674 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-video", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "1239875642389471056", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_video", + "h": 1080, + "w": 1920 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1239875642389471056", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_video", + "w": 1920, + "h": 1080 + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json new file mode 100644 index 00000000000..e13cdcb480a --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-imps.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [{ + "value": "Impression array should not be empty", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..c88e0fa7861 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + }, + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-1", + "seatbid": [], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [] +}] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json new file mode 100644 index 00000000000..ba46d76b598 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-bidder-object.json @@ -0,0 +1,48 @@ +{ + "expectedMakeRequestsErrors": [{ + "value": "Invalid imp.ext.bidder for impression index 0. Error Infomation: json: cannot unmarshal string into Go struct field ImpExtLemmaDigital.pid of type int", + "comparison": "literal" + }], + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": "1" + } + } + }], + "tmax": 1000 + }, + "httpCalls": [] + } \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json new file mode 100644 index 00000000000..4d3d795c185 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-ld-ext-object.json @@ -0,0 +1,42 @@ +{ + "expectedMakeRequestsErrors": [{ + "value": "Invalid imp.ext for impression index 0. Error Infomation: unexpected end of JSON input", + "comparison": "literal" + }], + "mockBidRequest": { + "app": { + "bundle": "com.ld.test", + "cat": [ + "IAB-1" + ], + "domain": "ld.com", + "id": "1", + "name": "LD Test", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ip": "0.0.0.0", + "language": "en", + "ua": "user-agent" + }, + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "h": 1080, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 1 + ], + "w": 1920 + } + }], + "tmax": 1000 + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json new file mode 100644 index 00000000000..036d35fb345 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [{ + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..37ebea4b7be --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..7c4813df43a --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..047dc4efd83 --- /dev/null +++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1", + "body": { + "id": "test-request-id-video", + "imp": [{ + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [1], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "aid": 1, + "pid": 1 + } + } + }], + "site": { + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/lemmadigital/params_test.go b/adapters/lemmadigital/params_test.go new file mode 100644 index 00000000000..4ba61319b33 --- /dev/null +++ b/adapters/lemmadigital/params_test.go @@ -0,0 +1,59 @@ +package lemmadigital + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// Tests for static/bidder-params/lemmadigital.json + +// Tests whether the schema supports the intended params. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schema. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderLemmadigital, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected params: %s \n Error: %s", validParam, err) + } + } +} + +// Tests whether the schema rejects unsupported imp.ext fields. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schema. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderLemmadigital, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed invalid/unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pid":1, "aid": 1}`, + `{"pid":2147483647, "aid": 2147483647}`, +} + +var invalidParams = []string{ + ``, + `null`, + `false`, + `0`, + `0.0`, + `[]`, + `{}`, + `{"pid":1}`, + `{"aid":1}`, + `{"pid":"1","aid":1}`, + `{"pid":1.0,"aid":"1"}`, + `{"pid":"1","aid":"1"}`, + `{"pid":false,"aid":true}`, +} diff --git a/adapters/liftoff/liftoff.go b/adapters/liftoff/liftoff.go new file mode 100644 index 00000000000..0dd881dfa2e --- /dev/null +++ b/adapters/liftoff/liftoff.go @@ -0,0 +1,149 @@ +package liftoff + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +const SupportedCurrency = "USD" + +type adapter struct { + Endpoint string +} + +type liftoffImpressionExt struct { + *adapters.ExtImpBidder + // Ext represents the vungle extension. + Ext openrtb_ext.ImpExtLiftoff `json:"vungle"` +} + +// Builder builds a new instance of the Liftoff adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{Endpoint: config.Endpoint}, nil +} + +// MakeRequests split impressions into bid requests and change them into the form that liftoff can handle. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + requestCopy := *request + for _, imp := range request.Imp { + // Check if imp comes with bid floor amount defined in a foreign currency + if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != SupportedCurrency { + // Convert to US dollars + convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, SupportedCurrency) + if err != nil { + errs = append(errs, fmt.Errorf("failed to convert currency (err)%s", err.Error())) + continue + } + + // Update after conversion. All imp elements inside request.Imp are shallow copies + // therefore, their non-pointer values are not shared memory and are safe to modify. + imp.BidFloorCur = SupportedCurrency + imp.BidFloor = convertedValue + } + + var impExt liftoffImpressionExt + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error())) + continue + } + + // get placement_reference_id & pub_app_store_id + var bidderImpExt openrtb_ext.ImpExtLiftoff + if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { + errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) + continue + } + + bidderImpExt.BidToken = requestCopy.User.BuyerUID + impExt.Ext = bidderImpExt + if newImpExt, err := json.Marshal(impExt); err == nil { + imp.Ext = newImpExt + } else { + errs = append(errs, errors.New("failed re-marshalling imp ext")) + continue + } + + imp.TagID = bidderImpExt.PlacementRefID + requestCopy.Imp = []openrtb2.Imp{imp} + // must make a shallow copy for pointers. + // If it is site object, need to construct an app with pub_store_id. + var requestAppCopy openrtb2.App + if request.App != nil { + requestAppCopy = *request.App + requestAppCopy.ID = bidderImpExt.PubAppStoreID + } else if request.Site != nil { + requestCopy.Site = nil + requestAppCopy = openrtb2.App{ + ID: bidderImpExt.PubAppStoreID, + } + } else { + errs = append(errs, errors.New("failed constructing app, must have app or site object in bid request")) + continue + } + + requestCopy.App = &requestAppCopy + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.Endpoint, + Body: requestJSON, + Headers: http.Header{ + "Content-Type": []string{"application/json"}, + "Accept": []string{"application/json"}, + "X-OpenRTB-Version": []string{"2.5"}, + }, + } + + requests = append(requests, requestData) + } + + return requests, errs +} + +// MakeBids collect bid response from liftoff and change them into the form that Prebid Server can handle. +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeVideo, + Seat: openrtb_ext.BidderName(seatBid.Seat), + } + + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, errs +} diff --git a/adapters/liftoff/liftoff_test.go b/adapters/liftoff/liftoff_test.go new file mode 100644 index 00000000000..4003bd21171 --- /dev/null +++ b/adapters/liftoff/liftoff_test.go @@ -0,0 +1,21 @@ +package liftoff + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + conf := config.Adapter{ + Endpoint: "https://liftoff.io/bit/t", + } + bidder, buildErr := Builder(openrtb_ext.BidderLiftoff, conf, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 667, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "liftofftest", bidder) +} diff --git a/adapters/liftoff/liftofftest/exemplary/app_video_instl.json b/adapters/liftoff/liftofftest/exemplary/app_video_instl.json new file mode 100644 index 00000000000..d13b2cd76ae --- /dev/null +++ b/adapters/liftoff/liftofftest/exemplary/app_video_instl.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json b/adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json new file mode 100644 index 00000000000..1cb9fdf7915 --- /dev/null +++ b/adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/exemplary/site_video_instl.json b/adapters/liftoff/liftofftest/exemplary/site_video_instl.json new file mode 100644 index 00000000000..ecb599cd785 --- /dev/null +++ b/adapters/liftoff/liftofftest/exemplary/site_video_instl.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "name": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/exemplary/site_video_rewarded.json b/adapters/liftoff/liftofftest/exemplary/site_video_rewarded.json new file mode 100644 index 00000000000..80754920b6e --- /dev/null +++ b/adapters/liftoff/liftofftest/exemplary/site_video_rewarded.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "name": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json b/adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json new file mode 100644 index 00000000000..1cb9fdf7915 --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json b/adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json new file mode 100644 index 00000000000..c8541d9d52d --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} diff --git a/adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json b/adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json new file mode 100644 index 00000000000..6095d9c5168 --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "placement_reference_id": "78910" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "78910", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "placement_reference_id": "78910" + }, + "vungle": { + "app_store_id": "", + "bid_token": "123", + "placement_reference_id": "78910" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 0*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/no_site_or_app_video_rewarded.json b/adapters/liftoff/liftofftest/supplemental/no_site_or_app_video_rewarded.json new file mode 100644 index 00000000000..ec846f1375c --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/no_site_or_app_video_rewarded.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "failed constructing app, must have app or site object in bid request", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_204.json b/adapters/liftoff/liftofftest/supplemental/response_code_204.json new file mode 100644 index 00000000000..4abefffc5c9 --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/response_code_204.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_400.json b/adapters/liftoff/liftofftest/supplemental/response_code_400.json new file mode 100644 index 00000000000..de5b0db421e --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/response_code_400.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_non_200.json b/adapters/liftoff/liftofftest/supplemental/response_code_non_200.json new file mode 100644 index 00000000000..17e1730ac2c --- /dev/null +++ b/adapters/liftoff/liftofftest/supplemental/response_code_non_200.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://liftoff.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123", + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + } + }, + "mockResponse": { + "status": 403, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403.*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/param_test.go b/adapters/liftoff/param_test.go new file mode 100644 index 00000000000..a5e5a61fb9a --- /dev/null +++ b/adapters/liftoff/param_test.go @@ -0,0 +1,46 @@ +package liftoff + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var validParams = []string{ + `{"app_store_id": "12345", "placement_reference_id": "12345"}`, +} + +var invalidParams = []string{ + `{}`, + // placement_reference_id + `{"placement_reference_id": "12345"}`, + `{"app_store_id": "12345"}`, + `{"app_store_id": null, "placement_reference_id": null}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderLiftoff, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderLiftoff, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} diff --git a/adapters/limelightDigital/limelightDigital.go b/adapters/limelightDigital/limelightDigital.go index 86d8df94233..d9920c72383 100644 --- a/adapters/limelightDigital/limelightDigital.go +++ b/adapters/limelightDigital/limelightDigital.go @@ -10,11 +10,11 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/limelightDigital/limelightDigital_test.go b/adapters/limelightDigital/limelightDigital_test.go index 0c4105b59c9..beb4222f5c8 100644 --- a/adapters/limelightDigital/limelightDigital_test.go +++ b/adapters/limelightDigital/limelightDigital_test.go @@ -1,12 +1,13 @@ package limelightDigital import ( - "github.com/stretchr/testify/assert" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/limelightDigital/params_test.go b/adapters/limelightDigital/params_test.go index 4ccef77c338..9a61e9ba7c9 100644 --- a/adapters/limelightDigital/params_test.go +++ b/adapters/limelightDigital/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/lm_kiviads/lmkiviads.go b/adapters/lm_kiviads/lmkiviads.go new file mode 100644 index 00000000000..03c17e6439c --- /dev/null +++ b/adapters/lm_kiviads/lmkiviads.go @@ -0,0 +1,161 @@ +package lmkiviads + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type bidType struct { + Type string `json:"type"` +} + +type bidExt struct { + Prebid bidType `json:"prebid"` +} + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + tmpl, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint URL template: %v", err) + } + + bidder := &adapter{ + endpoint: tmpl, + } + + return bidder, nil +} + +func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { + var impExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), + } + } + + var kiviExt openrtb_ext.ExtLmKiviads + if err := json.Unmarshal(impExt.Bidder, &kiviExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize LmKiviads extension: %v", err), + } + } + + endpointParams := macros.EndpointTemplateParams{ + Host: kiviExt.Env, + SourceId: kiviExt.Pid, + } + + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requestCopy := *request + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + endpoint, err := a.buildEndpointFromRequest(&imp) + if err != nil { + errs = append(errs, err) + continue + } + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + request := &adapters.RequestData{ + Method: http.MethodPost, + Body: requestJSON, + Uri: endpoint, + Headers: headers, + } + + requests = append(requests, request) + } + + return requests, errs +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder LmKiviads is unavailable. Please contact the bidder support.", + }} + } + + if err := adapters.CheckResponseStatusCodeForErrors(bidderRawResponse); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Array SeatBid cannot be empty", + }} + } + + return prepareBidResponse(bidResp.SeatBid) +} + +func prepareBidResponse(seats []openrtb2.SeatBid) (*adapters.BidderResponse, []error) { + errs := []error{} + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seats)) + + for _, seatBid := range seats { + for bidId, bid := range seatBid.Bid { + var bidExt bidExt + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + errs = append(errs, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse Bid[%d].Ext: %s", bidId, err.Error()), + }) + continue + } + + bidType, err := openrtb_ext.ParseBidType(bidExt.Prebid.Type) + if err != nil { + errs = append(errs, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bid[%d].Ext.Prebid.Type expects one of the following values: 'banner', 'native', 'video', 'audio', got '%s'", bidId, bidExt.Prebid.Type), + }) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[bidId], + BidType: bidType, + }) + } + } + + return bidResponse, errs +} diff --git a/adapters/lm_kiviads/lmkiviads_test.go b/adapters/lm_kiviads/lmkiviads_test.go new file mode 100644 index 00000000000..01bf47ef8a7 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviads_test.go @@ -0,0 +1,27 @@ +package lmkiviads + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderSmartHub, + config.Adapter{ + Endpoint: "http://pbs.kiviads.live/?pid={{.SourceId}}&host={{.Host}}", + }, + config.Server{ + ExternalUrl: "http://hosturl.com", + GvlID: 1, + DataCenter: "2", + }, + ) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "lmkiviadstest", bidder) +} diff --git a/adapters/lm_kiviads/lmkiviadstest/exemplary/banner.json b/adapters/lm_kiviads/lmkiviadstest/exemplary/banner.json new file mode 100644 index 00000000000..68bbd815c2b --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/exemplary/banner.json @@ -0,0 +1,279 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + }, + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b909b&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "1", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 0 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b909b&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "2", + "secure": 1, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "pos": 4 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": ["IAB12"], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "1", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid1", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "2", + "price": 2.4, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test3", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/exemplary/native.json b/adapters/lm_kiviads/lmkiviadstest/exemplary/native.json new file mode 100644 index 00000000000..efd8a80f488 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/exemplary/native.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b909b&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{}", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/exemplary/video.json b/adapters/lm_kiviads/lmkiviadstest/exemplary/video.json new file mode 100644 index 00000000000..1f694d510f1 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/exemplary/video.json @@ -0,0 +1,202 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b909b&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b909b" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/bad-response.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/bad-response.json new file mode 100644 index 00000000000..7380a97983b --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/bad-response.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b902" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b902&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b902" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-mediatype.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-mediatype.json new file mode 100644 index 00000000000..40c8e78d47b --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-mediatype.json @@ -0,0 +1,188 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b902" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=533faf0754cd43ceab591077781b902&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "533faf0754cd43ceab591077781b902" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "some": "value" + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid[1].Ext.Prebid.Type expects one of the following values: 'banner', 'native', 'video', 'audio', got ''", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid-0-bid.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid-0-bid.json new file mode 100644 index 00000000000..15e9a1df59a --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid-0-bid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [], + "expectedBidResponses": [{"currency":"USD","bids":[]}] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..6d6daebc8b7 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/empty-seatbid.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Array SeatBid cannot be empty", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-bidder-object.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-bidder-object.json new file mode 100644 index 00000000000..87ade656d2d --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-bidder-object.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": [] + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize LmKiviads extension: json: cannot unmarshal array into Go value of type openrtb_ext.ExtLmKiviads", + "comparison": "literal" + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-object.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-object.json new file mode 100644 index 00000000000..aa215eb3e34 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-ext-object.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": "" + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize bidder impression extension: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-mediatype.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-mediatype.json new file mode 100644 index 00000000000..bdf41988541 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/invalid-mediatype.json @@ -0,0 +1,190 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, { + "id": "id", + "impid": "2", + "price": 1.2, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test2", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "wrong" + } + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid[1].Ext.Prebid.Type expects one of the following values: 'banner', 'native', 'video', 'audio', got 'wrong'", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids":[ + { + "bid": { + "id": "id", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test1", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/status-204.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-204.json new file mode 100644 index 00000000000..c040fb79535 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-204.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/status-400.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-400.json new file mode 100644 index 00000000000..256f1134ccd --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-400.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/status-503.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-503.json new file mode 100644 index 00000000000..e0ca7f65d79 --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/status-503.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bidder LmKiviads is unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/lm_kiviads/lmkiviadstest/supplemental/unexpected-status.json b/adapters/lm_kiviads/lmkiviadstest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..d176e64105e --- /dev/null +++ b/adapters/lm_kiviads/lmkiviadstest/supplemental/unexpected-status.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.kiviads.live/?pid=dc230510f7eb516f0eb9a10e5913d3b5&host=kivi-stage", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "env": "kivi-stage", + "pid": "dc230510f7eb516f0eb9a10e5913d3b5" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Access is denied" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/rhythmone/params_test.go b/adapters/lm_kiviads/params_test.go similarity index 52% rename from adapters/rhythmone/params_test.go rename to adapters/lm_kiviads/params_test.go index 7d8cad47d53..1f3361b2581 100644 --- a/adapters/rhythmone/params_test.go +++ b/adapters/lm_kiviads/params_test.go @@ -1,12 +1,16 @@ -package rhythmone +package lmkiviads import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) +var validParams = []string{ + `{"env":"kivi-stage", "pid":"123456"}`, +} + func TestValidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -14,12 +18,27 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderRhythmone, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected rhythmone params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderLmKiviads, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected lmkiviads params: %s", validParam) } } } +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"some": "param"}`, + `{"env":"kivi-stage"}`, + `{"pid":"1234"}`, + `{"othervalue":"Lorem ipsum"}`, + `{"env":"kivi-stage", pid:""}`, + `{"env":"", pid:"1234"}`, +} + func TestInvalidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -27,31 +46,8 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderRhythmone, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderLmKiviads, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } } - -var validParams = []string{ - `{"placementId":"123", "zone":"12345", "path":"34567"}`, -} - -var invalidParams = []string{ - `{"placementId":"123", "zone":"12345", "path":34567}`, - `{"placementId":"123", "zone":12345, "path":"34567"}`, - `{"placementId":123, "zone":"12345", "path":"34567"}`, - `{"placementId":123, "zone":12345, "path":34567}`, - `{"placementId":123, "zone":12345, "path":"34567"}`, - `{"appId":"123", "bidfloor":0.01}`, - `{"publisherName": 100}`, - `{"placementId": 1234}`, - `{"zone": true}`, - ``, - `null`, - `nil`, - `true`, - `9`, - `[]`, - `{}`, -} diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index 7aa46080c73..c52a51a1003 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const unexpectedStatusCodeMessage = "Unexpected status code: %d. Run with request.debug = 1 for more info" diff --git a/adapters/lockerdome/lockerdome_test.go b/adapters/lockerdome/lockerdome_test.go index 1f807044b27..892fce2183a 100644 --- a/adapters/lockerdome/lockerdome_test.go +++ b/adapters/lockerdome/lockerdome_test.go @@ -3,9 +3,9 @@ package lockerdome import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/lockerdome/params_test.go b/adapters/lockerdome/params_test.go index c2c9185e374..9b90ec0b888 100644 --- a/adapters/lockerdome/params_test.go +++ b/adapters/lockerdome/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file tests static/bidder-params/lockerdome.json diff --git a/adapters/logan/logan.go b/adapters/logan/logan.go index db0888f8905..e5e76bca75b 100644 --- a/adapters/logan/logan.go +++ b/adapters/logan/logan.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/logan/logan_test.go b/adapters/logan/logan_test.go index da3a8741cff..219fbb50fe0 100644 --- a/adapters/logan/logan_test.go +++ b/adapters/logan/logan_test.go @@ -3,9 +3,9 @@ package logan import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/logan/params_test.go b/adapters/logan/params_test.go index 48da1912e61..55633b47c36 100644 --- a/adapters/logan/params_test.go +++ b/adapters/logan/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index ffbb783bafb..2bd60077ae0 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type LogicadAdapter struct { diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go index 98295cc4a28..551bd133a9c 100644 --- a/adapters/logicad/logicad_test.go +++ b/adapters/logicad/logicad_test.go @@ -3,9 +3,9 @@ package logicad import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/logicad/params_test.go b/adapters/logicad/params_test.go index eb34452811b..c45297d15c2 100644 --- a/adapters/logicad/params_test.go +++ b/adapters/logicad/params_test.go @@ -2,8 +2,9 @@ package logicad import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index 24717e6ba52..99a54deb82f 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type LunaMediaAdapter struct { diff --git a/adapters/lunamedia/lunamedia_test.go b/adapters/lunamedia/lunamedia_test.go index 961cd545303..c950ce4d25f 100644 --- a/adapters/lunamedia/lunamedia_test.go +++ b/adapters/lunamedia/lunamedia_test.go @@ -3,9 +3,9 @@ package lunamedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/lunamedia/params_test.go b/adapters/lunamedia/params_test.go index b4faeea1f77..12ecc65f420 100644 --- a/adapters/lunamedia/params_test.go +++ b/adapters/lunamedia/params_test.go @@ -2,8 +2,9 @@ package lunamedia import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/mabidder/mabidder.go b/adapters/mabidder/mabidder.go new file mode 100644 index 00000000000..9f7323c005d --- /dev/null +++ b/adapters/mabidder/mabidder.go @@ -0,0 +1,99 @@ +package mabidder + +import ( + "encoding/json" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type serverResponse struct { + Responses []bidResponse + PrivateIdStatus string `json:"-"` +} + +type bidResponse struct { + RequestID string `json:"requestId"` + Currency string `json:"currency"` + Width int32 `json:"width"` + Height int32 `json:"height"` + PlacementId string `json:"creativeId"` + Deal string `json:"dealId,omitempty"` + NetRevenue bool `json:"netRevenue"` + TimeToLiveSeconds int32 `json:"ttl"` + AdTag string `json:"ad"` + MediaType string `json:"mediaType"` + Meta meta `json:"meta"` + CPM float32 `json:"cpm"` +} + +type meta struct { + AdDomain []string `json:"advertiserDomains"` +} + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Mabidder adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response serverResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + for _, maBidResp := range response.Responses { + b := &adapters.TypedBid{ + Bid: &openrtb2.Bid{ + ID: maBidResp.RequestID, + ImpID: maBidResp.RequestID, + Price: float64(maBidResp.CPM), + AdM: maBidResp.AdTag, + W: int64(maBidResp.Width), + H: int64(maBidResp.Height), + CrID: maBidResp.PlacementId, + DealID: maBidResp.Deal, + ADomain: maBidResp.Meta.AdDomain, + }, + BidType: openrtb_ext.BidType(maBidResp.MediaType), + } + bidResponse.Bids = append(bidResponse.Bids, b) + if maBidResp.Currency != "" { + bidResponse.Currency = maBidResp.Currency + } + } + return bidResponse, nil +} diff --git a/adapters/mabidder/mabidder_test.go b/adapters/mabidder/mabidder_test.go new file mode 100644 index 00000000000..31c28788ad2 --- /dev/null +++ b/adapters/mabidder/mabidder_test.go @@ -0,0 +1,21 @@ +package mabidder + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderMabidder, config.Adapter{ + Endpoint: "https://prebid.ecdrsvc.com/pbs"}, + config.Server{ExternalUrl: "https://prebid.ecdrsvc.com/pbs", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "mabiddertest", bidder) +} diff --git a/adapters/mabidder/mabiddertest/exemplary/simple-app-banner.json b/adapters/mabidder/mabiddertest/exemplary/simple-app-banner.json new file mode 100644 index 00000000000..4d3cbdfd278 --- /dev/null +++ b/adapters/mabidder/mabiddertest/exemplary/simple-app-banner.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "ppid": "ppidtest" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid.ecdrsvc.com/pbs", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "ppid": "ppidtest" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "Responses": [ + { + "requestId": "1", + "currency": "CAD", + "width": 300, + "height": 250, + "creativeId": "6002677", + "dealId": "testdeal", + "netRevenue": false, + "ttl": 5, + "ad": "", + "meta": { + "advertiserDomains": [ + "https://www.loblaws.ca/" + ] + }, + "cpm": 3.5764000415802 + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "id": "test-request-id", + "bids": [ + { + "bid": { + "id": "1", + "impid": "1", + "price":3.5764000415802, + "adm": "", + "adomain": [ + "https://www.loblaws.ca/" + ], + "crid": "6002677", + "dealid": "testdeal", + "w": 300, + "h": 250 + } + } + ], + "cur": "USD" + } + ] +} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json b/adapters/mabidder/mabiddertest/supplemental/bad-request-example.json similarity index 62% rename from adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json rename to adapters/mabidder/mabiddertest/supplemental/bad-request-example.json index 587c952a042..8ab37e13079 100644 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/bad_response.json +++ b/adapters/mabidder/mabiddertest/supplemental/bad-request-example.json @@ -1,6 +1,12 @@ { "mockBidRequest": { "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -14,19 +20,24 @@ }, "ext": { "bidder": { - "pid": "213" + "ppid": "mabidder-test" } } } ] }, - "httpCalls": [ { "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", + "uri": "https://prebid.ecdrsvc.com/pbs", "body": { "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -40,7 +51,7 @@ }, "ext": { "bidder": { - "pid": "213" + "ppid": "mabidder-test" } } } @@ -48,15 +59,15 @@ } }, "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" + "status": 400, + "body": { + } } } ], - "expectedMakeBidsErrors": [ { - "value": "bad server body response", + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", "comparison": "literal" } ] diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/mabidder/mabiddertest/supplemental/bad-response-malformed.json similarity index 66% rename from adapters/kubient/kubienttest/supplemental/bad_response.json rename to adapters/mabidder/mabiddertest/supplemental/bad-response-malformed.json index 832dc975088..f62ed2a7a61 100644 --- a/adapters/kubient/kubienttest/supplemental/bad_response.json +++ b/adapters/mabidder/mabiddertest/supplemental/bad-response-malformed.json @@ -1,6 +1,12 @@ { "mockBidRequest": { "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -14,7 +20,7 @@ }, "ext": { "bidder": { - "zoneid": "23" + "ppid": "mabidder-test" } } } @@ -23,9 +29,15 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", + "uri": "https://prebid.ecdrsvc.com/pbs", "body": { "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -39,7 +51,7 @@ }, "ext": { "bidder": { - "zoneid": "23" + "ppid": "mabidder-test" } } } @@ -47,14 +59,13 @@ } }, "mockResponse": { - "status": 200, - "body": "{\"id\"data.lost" + "status": 200 } } ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "unexpected end of JSON input", "comparison": "literal" } ] diff --git a/adapters/mabidder/mabiddertest/supplemental/bad-response-status-500.json b/adapters/mabidder/mabiddertest/supplemental/bad-response-status-500.json new file mode 100644 index 00000000000..8ee59384071 --- /dev/null +++ b/adapters/mabidder/mabiddertest/supplemental/bad-response-status-500.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "ppid": "mabidder-test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://prebid.ecdrsvc.com/pbs", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "ppid": "mabidder-test" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": { + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json b/adapters/mabidder/mabiddertest/supplemental/no-content-response.json similarity index 66% rename from adapters/suntContent/suntContenttest/supplemental/status_no_content.json rename to adapters/mabidder/mabiddertest/supplemental/no-content-response.json index 8eda774c657..640e78dcec8 100644 --- a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json +++ b/adapters/mabidder/mabiddertest/supplemental/no-content-response.json @@ -1,6 +1,12 @@ { "mockBidRequest": { "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -9,16 +15,12 @@ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, "ext": { "bidder": { - "adUnitId": "example-tag-id" + "ppid": "mabidder-test" } } } @@ -27,12 +29,15 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", + "uri": "https://prebid.ecdrsvc.com/pbs", "body": { - "cur": [ - "EUR" - ], "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"87857b31-8942-4646-ae80-ab9c95bf3fab" + }, "imp": [ { "id": "test-imp-id", @@ -41,17 +46,12 @@ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, - "tagid": "example-tag-id", "ext": { "bidder": { - "adUnitId": "example-tag-id" + "ppid": "mabidder-test" } } } @@ -59,8 +59,11 @@ } }, "mockResponse": { - "status": 204 + "status": 204, + "body": { + } } } - ] -} \ No newline at end of file + ], + "expectedBidResponses": [] +} diff --git a/adapters/suntContent/params_test.go b/adapters/mabidder/params_test.go similarity index 65% rename from adapters/suntContent/params_test.go rename to adapters/mabidder/params_test.go index 653ed948d46..878e278438d 100644 --- a/adapters/suntContent/params_test.go +++ b/adapters/mabidder/params_test.go @@ -1,10 +1,10 @@ -package suntContent +package mabidder import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { @@ -14,7 +14,7 @@ func TestValidParams(t *testing.T) { } for _, p := range validParams { - if err := validator.Validate(openrtb_ext.BidderSuntContent, json.RawMessage(p)); err != nil { + if err := validator.Validate(openrtb_ext.BidderMabidder, json.RawMessage(p)); err != nil { t.Errorf("Schema rejected valid params: %s", p) } } @@ -27,17 +27,24 @@ func TestInvalidParams(t *testing.T) { } for _, p := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderSuntContent, json.RawMessage(p)); err == nil { + if err := validator.Validate(openrtb_ext.BidderMabidder, json.RawMessage(p)); err == nil { t.Errorf("Schema allowed invalid params: %s", p) } } } var validParams = []string{ - `{"adUnitId": "1234"}`, - `{"adUnitId": "AB12"}`, + `{"ppid": ""}`, + `{"ppid": "testppid"}`, } var invalidParams = []string{ - `{"adUnitId": 42}`, + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"ppid": 42}`, } diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go index 32b84c6e65a..41097f62ab6 100644 --- a/adapters/madvertise/madvertise.go +++ b/adapters/madvertise/madvertise.go @@ -8,11 +8,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/madvertise/madvertise_test.go b/adapters/madvertise/madvertise_test.go index 1d48b6dab3c..3d412b1e154 100644 --- a/adapters/madvertise/madvertise_test.go +++ b/adapters/madvertise/madvertise_test.go @@ -3,9 +3,9 @@ package madvertise import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/madvertise/params_test.go b/adapters/madvertise/params_test.go index 4b73d57d43e..55454399155 100644 --- a/adapters/madvertise/params_test.go +++ b/adapters/madvertise/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/Madvertise.json diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index 6bba910aee0..c75b4f7ad6b 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MarsmediaAdapter struct { diff --git a/adapters/marsmedia/marsmedia_test.go b/adapters/marsmedia/marsmedia_test.go index 8fe01a72f73..6c75ef76c37 100644 --- a/adapters/marsmedia/marsmedia_test.go +++ b/adapters/marsmedia/marsmedia_test.go @@ -3,9 +3,9 @@ package marsmedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/marsmedia/params_test.go b/adapters/marsmedia/params_test.go index f78ad1c3dc4..ee79015b05c 100644 --- a/adapters/marsmedia/params_test.go +++ b/adapters/marsmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/marsmedia.json diff --git a/adapters/medianet/medianet.go b/adapters/medianet/medianet.go index a5bf8e60df4..f84705aaa28 100644 --- a/adapters/medianet/medianet.go +++ b/adapters/medianet/medianet.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/medianet/medianet_test.go b/adapters/medianet/medianet_test.go index 6403b3f2eb0..097e8e48727 100644 --- a/adapters/medianet/medianet_test.go +++ b/adapters/medianet/medianet_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/medianet/params_test.go b/adapters/medianet/params_test.go index a1e5e834ba6..7c999a66428 100644 --- a/adapters/medianet/params_test.go +++ b/adapters/medianet/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/medianet.json diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index 99d1946d3f1..712c6cd8928 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -7,10 +7,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MgidAdapter struct { diff --git a/adapters/mgid/mgid_test.go b/adapters/mgid/mgid_test.go index fba3d8b09c3..21311f5477d 100644 --- a/adapters/mgid/mgid_test.go +++ b/adapters/mgid/mgid_test.go @@ -3,9 +3,9 @@ package mgid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mgidX/mgidX.go b/adapters/mgidX/mgidX.go index 29d2df4617c..084fdaebca9 100644 --- a/adapters/mgidX/mgidX.go +++ b/adapters/mgidX/mgidX.go @@ -7,9 +7,9 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/mgidX/mgidX_test.go b/adapters/mgidX/mgidX_test.go index 5a71750788a..7ff4eb93627 100644 --- a/adapters/mgidX/mgidX_test.go +++ b/adapters/mgidX/mgidX_test.go @@ -3,9 +3,9 @@ package mgidX import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mgidX/params_test.go b/adapters/mgidX/params_test.go index 85779ec6e8c..b3d80207811 100644 --- a/adapters/mgidX/params_test.go +++ b/adapters/mgidX/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 6684d0c3fc2..24e13e26584 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/mobfoxpb/mobfoxpb_test.go b/adapters/mobfoxpb/mobfoxpb_test.go index 401396adc8d..b3aa4ae4b2c 100644 --- a/adapters/mobfoxpb/mobfoxpb_test.go +++ b/adapters/mobfoxpb/mobfoxpb_test.go @@ -3,9 +3,9 @@ package mobfoxpb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/mobfoxpb/params_test.go b/adapters/mobfoxpb/params_test.go index 799fdcfa61b..ea785163609 100644 --- a/adapters/mobfoxpb/params_test.go +++ b/adapters/mobfoxpb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the mobfoxpb schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index f46fc5913a3..24461ade471 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type MobileFuseAdapter struct { @@ -27,6 +27,10 @@ type BidExt struct { Mf ExtMf `json:"mf"` } +type ExtSkadn struct { + Skadn json.RawMessage `json:"skadn"` +} + // Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { template, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -79,7 +83,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest, for _, seatbid := range incomingBidResponse.SeatBid { for i := range seatbid.Bid { - bidType := adapter.getBidType(seatbid.Bid[i]) + bidType := getBidType(seatbid.Bid[i]) seatbid.Bid[i].Ext = nil outgoingBidResponse.Bids = append(outgoingBidResponse.Bids, &adapters.TypedBid{ @@ -95,22 +99,18 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest, func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest) - + mobileFuseExtension, errs := getFirstMobileFuseExtension(bidRequest) if errs != nil { return nil, errs } endpoint, err := adapter.getEndpoint(mobileFuseExtension) - if err != nil { return nil, append(errs, err) } - validImps := adapter.getValidImps(bidRequest, mobileFuseExtension) - - if len(validImps) == 0 { - err := fmt.Errorf("No valid imps") + validImps, err := getValidImps(bidRequest, mobileFuseExtension) + if err != nil { errs = append(errs, err) return nil, errs } @@ -118,7 +118,6 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) ( mobileFuseBidRequest := *bidRequest mobileFuseBidRequest.Imp = validImps body, err := json.Marshal(mobileFuseBidRequest) - if err != nil { return nil, append(errs, err) } @@ -135,7 +134,7 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) ( }, errs } -func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { +func getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse var errs []error @@ -162,13 +161,23 @@ func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2. return &mobileFuseImpExtension, errs } +func getMobileFuseExtensionForImp(imp *openrtb2.Imp, mobileFuseImpExtension *openrtb_ext.ExtImpMobileFuse) error { + var bidder_imp_extension adapters.ExtImpBidder + + err := json.Unmarshal(imp.Ext, &bidder_imp_extension) + if err != nil { + return err + } + + return json.Unmarshal(bidder_imp_extension.Bidder, &mobileFuseImpExtension) +} + func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) (string, error) { publisher_id := strconv.Itoa(ext.PublisherId) - url, errs := macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{PublisherID: publisher_id}) - - if errs != nil { - return "", errs + url, err := macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{PublisherID: publisher_id}) + if err != nil { + return "", err } if ext.TagidSrc == "ext" { @@ -178,23 +187,45 @@ func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) return url, nil } -func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb2.Imp { +func getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) ([]openrtb2.Imp, error) { var validImps []openrtb2.Imp for _, imp := range bidRequest.Imp { if imp.Banner != nil || imp.Video != nil || imp.Native != nil { + err := getMobileFuseExtensionForImp(&imp, ext) + if err != nil { + return nil, err + } + imp.TagID = strconv.Itoa(ext.PlacementId) - imp.Ext = nil - validImps = append(validImps, imp) - break + var extSkadn ExtSkadn + err = json.Unmarshal(imp.Ext, &extSkadn) + if err != nil { + return nil, err + } + + if extSkadn.Skadn != nil { + imp.Ext, err = json.Marshal(map[string]json.RawMessage{"skadn": extSkadn.Skadn}) + if err != nil { + return nil, err + } + } else { + imp.Ext = nil + } + + validImps = append(validImps, imp) } } - return validImps + if len(validImps) == 0 { + return nil, fmt.Errorf("No valid imps") + } + + return validImps, nil } -func (adapter *MobileFuseAdapter) getBidType(bid openrtb2.Bid) openrtb_ext.BidType { +func getBidType(bid openrtb2.Bid) openrtb_ext.BidType { if bid.Ext != nil { var bidExt BidExt err := json.Unmarshal(bid.Ext, &bidExt) diff --git a/adapters/mobilefuse/mobilefuse_test.go b/adapters/mobilefuse/mobilefuse_test.go index 09d46faff66..e1a3a018bb9 100644 --- a/adapters/mobilefuse/mobilefuse_test.go +++ b/adapters/mobilefuse/mobilefuse_test.go @@ -3,9 +3,9 @@ package mobilefuse import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json new file mode 100644 index 00000000000..c31cb1ced55 --- /dev/null +++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "ext": { + "bidder": { + "placement_id": 123456, + "pub_id": 1234 + } + } + }, + { + "id": "2", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "native": { + "ver": "1.2", + "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}" + }, + "ext": { + "bidder": { + "placement_id": 234567, + "pub_id": 1234 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1234", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "tagid": "123456" + }, + { + "id": "2", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "native": { + "ver": "1.2", + "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}" + }, + "tagid": "234567" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "mobilefuse", + "bid": [ + { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 50, + "w": 320, + "ext": { + "mf": { + "media_type": "banner" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json index 568e51af61b..15ba4f96d6b 100644 --- a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json +++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json @@ -57,6 +57,18 @@ ] }, "tagid": "123456" + }, + { + "id": "2", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "tagid": "234567" } ] } diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json new file mode 100644 index 00000000000..e8fd66b1111 --- /dev/null +++ b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "skadn": { + "versions": [ + "2.0", + "2.1" + ], + "sourceapp": "11111", + "skadnetids": [ + "424m5254lk.skadnetwork", + "4fzdc2evr5.skadnetwork" + ] + }, + "bidder": { + "placement_id": 999999, + "pub_id": 1111, + "tagid_src": "ext" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1111&tagid_src=ext", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "999999", + "ext": { + "skadn": { + "versions": [ + "2.0", + "2.1" + ], + "sourceapp": "11111", + "skadnetids": [ + "424m5254lk.skadnetwork", + "4fzdc2evr5.skadnetwork" + ] + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/mobilefuse/params_test.go b/adapters/mobilefuse/params_test.go index dbfd8894e70..8599af5ece0 100644 --- a/adapters/mobilefuse/params_test.go +++ b/adapters/mobilefuse/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(test *testing.T) { diff --git a/adapters/motorik/motorik.go b/adapters/motorik/motorik.go index c804c0951fc..95bb4837c9a 100644 --- a/adapters/motorik/motorik.go +++ b/adapters/motorik/motorik.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/motorik/motorik_test.go b/adapters/motorik/motorik_test.go index 1a14fedac7b..5bdd102ddd3 100644 --- a/adapters/motorik/motorik_test.go +++ b/adapters/motorik/motorik_test.go @@ -3,9 +3,9 @@ package motorik import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/motorik/params_test.go b/adapters/motorik/params_test.go index 9ee05b58e07..2cfdf0f965b 100644 --- a/adapters/motorik/params_test.go +++ b/adapters/motorik/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go deleted file mode 100644 index d8e3c709525..00000000000 --- a/adapters/nanointeractive/nanointeractive.go +++ /dev/null @@ -1,164 +0,0 @@ -package nanointeractive - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type NanoInteractiveAdapter struct { - endpoint string -} - -func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - - var errs []error - var validImps []openrtb2.Imp - - var adapterRequests []*adapters.RequestData - var referer string = "" - - for i := 0; i < len(bidRequest.Imp); i++ { - - ref, err := checkImp(&bidRequest.Imp[i]) - - // If the parsing is failed, remove imp and add the error. - if err != nil { - errs = append(errs, err) - continue - } - if referer == "" && ref != "" { - referer = ref - } - validImps = append(validImps, bidRequest.Imp[i]) - } - - if len(validImps) == 0 { - errs = append(errs, fmt.Errorf("no impressions in the bid request")) - return nil, errs - } - - // set referer origin - if referer != "" { - if bidRequest.Site == nil { - bidRequest.Site = &openrtb2.Site{} - } - bidRequest.Site.Ref = referer - } - - bidRequest.Imp = validImps - - reqJSON, err := json.Marshal(bidRequest) - if err != nil { - errs = append(errs, err) - return nil, errs - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") - if bidRequest.Device != nil { - headers.Add("User-Agent", bidRequest.Device.UA) - headers.Add("X-Forwarded-For", bidRequest.Device.IP) - } - if bidRequest.Site != nil { - headers.Add("Referer", bidRequest.Site.Page) - } - - // set user's cookie - if bidRequest.User != nil && bidRequest.User.BuyerUID != "" { - headers.Add("Cookie", "Nano="+bidRequest.User.BuyerUID) - } - - adapterRequests = append(adapterRequests, &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: reqJSON, - Headers: headers, - }) - - return adapterRequests, errs -} - -func (a *NanoInteractiveAdapter) MakeBids( - internalRequest *openrtb2.BidRequest, - externalRequest *adapters.RequestData, - response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - - if response.StatusCode == http.StatusNoContent { - return nil, nil - } else if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: "Invalid request.", - }} - } else if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected HTTP status %d.", response.StatusCode), - }} - } - - var openRtbBidResponse openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("bad server body response"), - }} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRtbBidResponse.SeatBid[0].Bid)) - bidResponse.Currency = openRtbBidResponse.Cur - - sb := openRtbBidResponse.SeatBid[0] - for i := 0; i < len(sb.Bid); i++ { - if !(sb.Bid[i].Price > 0) { - continue - } - bid := sb.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, - }) - } - return bidResponse, nil -} - -func checkImp(imp *openrtb2.Imp) (string, error) { - // We support only banner impression - if imp.Banner == nil { - return "", fmt.Errorf("invalid MediaType. NanoInteractive only supports Banner type. ImpID=%s", imp.ID) - } - - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return "", fmt.Errorf("ext not provided; ImpID=%s", imp.ID) - } - - var nanoExt openrtb_ext.ExtImpNanoInteractive - if err := json.Unmarshal(bidderExt.Bidder, &nanoExt); err != nil { - return "", fmt.Errorf("ext.bidder not provided; ImpID=%s", imp.ID) - } - if nanoExt.Pid == "" { - return "", fmt.Errorf("pid is empty; ImpID=%s", imp.ID) - } - - if nanoExt.Ref != "" { - return string(nanoExt.Ref), nil - } - - return "", nil -} - -// Builder builds a new instance of the NanoInteractive adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &NanoInteractiveAdapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} diff --git a/adapters/nanointeractive/nanointeractive_test.go b/adapters/nanointeractive/nanointeractive_test.go deleted file mode 100644 index fb108b8dd58..00000000000 --- a/adapters/nanointeractive/nanointeractive_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package nanointeractive - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderNanoInteractive, config.Adapter{ - Endpoint: "https://ad.audiencemanager.de/hbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "nanointeractivetest", bidder) -} diff --git a/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json b/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json deleted file mode 100644 index 727010b93fe..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/exemplary/simple-banner.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "nanointeractive", - "bid": [{ - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "
", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }] - } - ], - "bidid": "5a7789eg2662b524d8d7264a96", - "cur": "EUR" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json b/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json deleted file mode 100644 index 631dc99e5a8..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/invalid-params.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id-1", - "banner": {}, - "ext": { - "bidder": {} - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [{"w": 300, "h": 250}] - } - }, - { - "id": "test-imp-id-4", - "video": {}, - "ext": { - "bidder": {} - } - }, - { - "id": "test-imp-id-5", - "audio": { - "startdelay": 0, - "api": [] - }, - "ext": { - "bidder": {} - } - } - ], - "site": { - "id": "siteID", - "publisher": { - "id": "1234" - } - }, - "device": { - "os": "android" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "pid is empty; ImpID=test-imp-id-1", - "comparison": "literal" - }, - { - "value": "ext.bidder not provided; ImpID=test-imp-id-2", - "comparison": "literal" - }, - { - "value": "ext not provided; ImpID=test-imp-id-3", - "comparison": "literal" - }, - { - "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-4", - "comparison": "literal" - }, - { - "value": "invalid MediaType. NanoInteractive only supports Banner type. ImpID=test-imp-id-5", - "comparison": "literal" - }, - { - "value": "no impressions in the bid request", - "comparison": "literal" - } - ] -} diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json b/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json deleted file mode 100644 index 4f501901e2a..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/multi-param.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "ref": "https://nanointeractive.com" - } - } - }, - { - "id": "test-imp-id2", - "banner": { - "format": [{"w": 300, "h": 250}] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "nq": ["search query"], - "category": "Automotive", - "subId": "a23", - "ref": "https://nanointeractive.com" - } - } - } - ], - "device": { - "ip": "127.0.0.1", - "ua": "user_agent" - }, - "user": { - "buyeruid": "userId" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "ref": "https://nanointeractive.com" - } - } - }, - { - "id": "test-imp-id2", - "banner": { - "format": [{ "w": 300,"h": 250} - ] - }, - "ext": { - "bidder": { - "pid": "58bfec94eb0a1916fa380163", - "nq": ["search query"], - "category": "Automotive", - "subId": "a23", - "ref": "https://nanointeractive.com" - } - } - } - ], - "site": { - "ref": "https://nanointeractive.com" - }, - "device": { - "ip": "127.0.0.1", - "ua": "user_agent" - }, - "user": { - "buyeruid": "userId" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "nanointeractive", - "bid": [{ - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - },{ - "id": "2", - "impid": "test-imp-id2", - "price": 0, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }] - } - ], - "bidid": "5a7789eg2662b524d8d7264a96", - "cur": "EUR" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "1", - "impid": "test-imp-id", - "price": 0.4580126, - "adm": "", - "adid": "test_ad_id", - "adomain": ["audiencemanager.de"], - "cid": "test_cid", - "crid": "test_banner_crid", - "h": 250, - "w": 300 - }, - "type": "banner" - }] - } - ] -} diff --git a/adapters/nanointeractive/params_test.go b/adapters/nanointeractive/params_test.go deleted file mode 100644 index a50425c770b..00000000000 --- a/adapters/nanointeractive/params_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package nanointeractive - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -// This file actually intends to test static/bidder-params/nanointeractive.json -// -// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.nanointeracive - -// TestValidParams makes sure that the NanoInteractive schema accepts all imp.ext fields which we intend to support. -func TestValidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected NanoInteractive params: %s", validParam) - } - } -} - -// TestInvalidParams makes sure that the Marsmedia schema rejects all the imp.ext fields we don't support. -func TestInvalidParams(t *testing.T) { - validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") - if err != nil { - t.Fatalf("Failed to fetch the json-schemas. %v", err) - } - - for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderNanoInteractive, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"pid": "dafad098"}`, - `{"pid":"dfasfda","nq":["search query"]}`, - `{"pid":"dfasfda","nq":["search query"],"subId":"any string value","category":"any string value"}`, -} - -var invalidParams = []string{ - `{"pid":123}`, - `{"pid":"12323","nq":"search query not an array"}`, - `{"pid":"12323","category":1}`, - `{"pid":"12323","subId":23}`, - ``, - `null`, - `true`, - `9`, - `1.2`, - `[]`, - `{}`, - `placementId`, - `zone`, - `zoneId`, -} diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go index f24bb6f7df8..966f96de47a 100644 --- a/adapters/nextmillennium/nextmillennium.go +++ b/adapters/nextmillennium/nextmillennium.go @@ -6,14 +6,15 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { endpoint string + nmmFlags []string } type nmExtPrebidStoredRequest struct { @@ -22,8 +23,12 @@ type nmExtPrebidStoredRequest struct { type nmExtPrebid struct { StoredRequest nmExtPrebidStoredRequest `json:"storedrequest"` } +type nmExtNMM struct { + NmmFlags []string `json:"nmmFlags,omitempty"` +} type nextMillJsonExt struct { - Prebid nmExtPrebid `json:"prebid"` + Prebid nmExtPrebid `json:"prebid"` + NextMillennium nmExtNMM `json:"nextMillennium,omitempty"` } // MakeRequests prepares request information for prebid-server core @@ -77,7 +82,7 @@ func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtNextMillennium, err } func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) (*adapters.RequestData, error) { - newBidRequest := createBidRequest(prebidBidRequest, params) + newBidRequest := createBidRequest(prebidBidRequest, params, adapter.nmmFlags) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -96,7 +101,7 @@ func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidReques Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *openrtb2.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium, flags []string) *openrtb2.BidRequest { placementID := params.PlacementID if params.GroupID != "" { @@ -122,6 +127,7 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext } ext := nextMillJsonExt{} ext.Prebid.StoredRequest.ID = placementID + ext.NextMillennium.NmmFlags = flags jsonExt, err := json.Marshal(ext) if err != nil { return prebidBidRequest @@ -169,7 +175,15 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR // Builder builds a new instance of the NextMillennium adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + var info nmExtNMM + if config.ExtraAdapterInfo != "" { + if err := json.Unmarshal([]byte(config.ExtraAdapterInfo), &info); err != nil { + return nil, fmt.Errorf("invalid extra info: %v", err) + } + } + return &adapter{ endpoint: config.Endpoint, + nmmFlags: info.NmmFlags, }, nil } diff --git a/adapters/nextmillennium/nextmillennium_test.go b/adapters/nextmillennium/nextmillennium_test.go index bd75596691f..6e51edc5e17 100644 --- a/adapters/nextmillennium/nextmillennium_test.go +++ b/adapters/nextmillennium/nextmillennium_test.go @@ -3,9 +3,10 @@ package nextmillennium import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -18,3 +19,15 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "nextmillenniumtest", bidder) } +func TestWithExtraInfo(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderNextMillennium, config.Adapter{ + Endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction", + ExtraAdapterInfo: "{\"nmmFlags\":[\"flag1\",\"flag2\"]}", + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + bidderNextMillennium, _ := bidder.(*adapter) + assert.Equal(t, bidderNextMillennium.nmmFlags, []string{"flag1", "flag2"}) +} diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json index 7a628d0fd91..19212134463 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-empty-group-id.json @@ -33,6 +33,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" @@ -54,6 +55,7 @@ ] }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json index 3538113014d..129c025e1da 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id.json @@ -44,6 +44,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" @@ -67,6 +68,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json index 25c1f36deff..2ddf9bee948 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-group-id_app.json @@ -40,6 +40,7 @@ "domain": "www.example.com" }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" @@ -63,6 +64,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json index 79c9894f424..4db65ec46cd 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-only-width.json @@ -29,6 +29,7 @@ "domain": "www.example.com" }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;;www.example.com" @@ -41,6 +42,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;;www.example.com" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json index 6af7816eb21..f7da0784728 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-with-wh.json @@ -30,6 +30,7 @@ "domain": "www.example.com" }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" @@ -43,6 +44,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;www.example.com" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json index 181dab3548b..d27dffafaf1 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-domain.json @@ -34,6 +34,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;" @@ -57,6 +58,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json index f2422033b81..c5b9276700d 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner-wo-size.json @@ -32,6 +32,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;" @@ -53,6 +54,7 @@ ] }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;320x250;" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json index fa8834fa998..df736d79757 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json @@ -34,6 +34,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" @@ -57,6 +58,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json index cc9f9eb980b..2d357ffb99c 100644 --- a/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/empty-banner-obj.json @@ -21,6 +21,7 @@ "body":{ "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;;" @@ -31,6 +32,7 @@ { "banner": {}, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "g7819;;" diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json index dd070d62427..8f3588de144 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json @@ -33,6 +33,7 @@ "body": { "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" @@ -56,6 +57,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json index 3e8047fae54..f7b23ac9bcf 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json @@ -29,6 +29,7 @@ "body": { "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" @@ -48,6 +49,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json index c4710a376d0..699be4e9aad 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json @@ -29,6 +29,7 @@ "body": { "id": "testid", "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" @@ -48,6 +49,7 @@ "w": 320 }, "ext": { + "nextMillennium": {}, "prebid": { "storedrequest": { "id": "7819" diff --git a/adapters/nextmillennium/params_test.go b/adapters/nextmillennium/params_test.go index 50a9c377f70..d8ae93d2c5a 100644 --- a/adapters/nextmillennium/params_test.go +++ b/adapters/nextmillennium/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go deleted file mode 100755 index 70dd60ab9fe..00000000000 --- a/adapters/ninthdecimal/ninthdecimal.go +++ /dev/null @@ -1,241 +0,0 @@ -package ninthdecimal - -import ( - "encoding/json" - "fmt" - "net/http" - "text/template" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type NinthDecimalAdapter struct { - EndpointTemplate *template.Template -} - -// MakeRequests prepares request information for prebid-server core -func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) - if len(request.Imp) == 0 { - errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) - return nil, errs - } - pub2impressions, imps, err := getImpressionsInfo(request.Imp) - if len(imps) == 0 { - return nil, err - } - errs = append(errs, err...) - - if len(pub2impressions) == 0 { - return nil, errs - } - - result := make([]*adapters.RequestData, 0, len(pub2impressions)) - for k, imps := range pub2impressions { - bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) - if err != nil { - errs = append(errs, err) - return nil, errs - } else { - result = append(result, bidRequest) - } - } - return result, errs -} - -// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp, []openrtb2.Imp, []error) { - errors := make([]error, 0, len(imps)) - resImps := make([]openrtb2.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp) - - for _, imp := range imps { - impExt, err := getImpressionExt(&imp) - if err != nil { - errors = append(errors, err) - continue - } - if err := validateImpression(impExt); err != nil { - errors = append(errors, err) - continue - } - //dispatchImpressions - //Group impressions by NinthDecimal-specific parameters `pubid - if err := compatImpression(&imp); err != nil { - errors = append(errors, err) - continue - } - if res[*impExt] == nil { - res[*impExt] = make([]openrtb2.Imp, 0) - } - res[*impExt] = append(res[*impExt], imp) - resImps = append(resImps, imp) - } - return res, resImps, errors -} - -func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { - if impExt.PublisherID == "" { - return &errortypes.BadInput{Message: "No pubid value provided"} - } - return nil -} - -// Alter impression info to comply with NinthDecimal platform requirements -func compatImpression(imp *openrtb2.Imp) error { - imp.Ext = nil //do not forward ext to NinthDecimal platform - if imp.Banner != nil { - return compatBannerImpression(imp) - } - return nil -} - -func compatBannerImpression(imp *openrtb2.Imp) error { - // Create a copy of the banner, since imp is a shallow copy of the original. - - bannerCopy := *imp.Banner - banner := &bannerCopy - //As banner.w/h are required fields for NinthDecimal platform - take the first format entry - if banner.W == nil || banner.H == nil { - if len(banner.Format) == 0 { - return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"} - } - format := banner.Format[0] - banner.Format = banner.Format[1:] - banner.W = &format.W - banner.H = &format.H - imp.Banner = banner - } - return nil -} - -func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - var NinthDecimalExt openrtb_ext.ExtImpNinthDecimal - if err := json.Unmarshal(bidderExt.Bidder, &NinthDecimalExt); err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - return &NinthDecimalExt, nil -} - -func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) (*adapters.RequestData, error) { - newBidRequest := createBidRequest(prebidBidRequest, params, imps) - reqJSON, err := json.Marshal(newBidRequest) - if err != nil { - return nil, err - } - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") - - url, err := adapter.buildEndpointURL(params) - if err != nil { - return nil, err - } - - return &adapters.RequestData{ - Method: "POST", - Uri: url, - Body: reqJSON, - Headers: headers}, nil -} - -func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) *openrtb2.BidRequest { - bidRequest := *prebidBidRequest - bidRequest.Imp = imps - for idx := range bidRequest.Imp { - imp := &bidRequest.Imp[idx] - imp.TagID = params.Placement - } - if bidRequest.Site != nil { - // Need to copy Site as Request is a shallow copy - siteCopy := *bidRequest.Site - bidRequest.Site = &siteCopy - bidRequest.Site.Publisher = nil - bidRequest.Site.Domain = "" - } - if bidRequest.App != nil { - // Need to copy App as Request is a shallow copy - appCopy := *bidRequest.App - bidRequest.App = &appCopy - bidRequest.App.Publisher = nil - } - return &bidRequest -} - -// Builds enpoint url based on adapter-specific pub settings from imp.ext -func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpNinthDecimal) (string, error) { - endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} - return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) -} - -// MakeBids translates NinthDecimal bid response to prebid-server specific format -func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var msg = "" - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - if response.StatusCode != http.StatusOK { - msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - - } - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - msg = fmt.Sprintf("Bad server response: %d", err) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - } - if len(bidResp.SeatBid) != 1 { - var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} - } - - seatBid := bidResp.SeatBid[0] - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) - - for i := 0; i < len(seatBid.Bid); i++ { - bid := seatBid.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), - }) - } - return bidResponse, nil -} - -// getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Video != nil { - return openrtb_ext.BidTypeVideo - } - } - return openrtb_ext.BidTypeBanner -} - -// Builder builds a new instance of the NinthDecimal adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - template, err := template.New("endpointTemplate").Parse(config.Endpoint) - if err != nil { - return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) - } - - bidder := &NinthDecimalAdapter{ - EndpointTemplate: template, - } - return bidder, nil -} diff --git a/adapters/ninthdecimal/ninthdecimal_test.go b/adapters/ninthdecimal/ninthdecimal_test.go deleted file mode 100755 index 8932ac22f58..00000000000 --- a/adapters/ninthdecimal/ninthdecimal_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package ninthdecimal - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "ninthdecimaltest", bidder) -} - -func TestEndpointTemplateMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderNinthDecimal, config.Adapter{ - Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - assert.Error(t, buildErr) -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json deleted file mode 100644 index d2184fa06b6..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "id": "testimpid", - "tagid": "dummyplacement", - "banner": { - "format": [{ - "w": 320, - "h": 250 - }, { - "w": 320, - "h": 300 - }], - "w": 320, - "h": 250 - } - - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json deleted file mode 100644 index 4ad093e0648..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "video": { - "mimes": [ - "video/mp4" - ], - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "id": "testimpid", - "tagid": "dummyplacement", - "video": { - "mimes": [ - "video/mp4" - ], - "w": 640, - "h": 480 - } - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "video" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json deleted file mode 100644 index ca48812b4df..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test", - "domain": "test.com" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "No impression in the bid request", - "comparison": "literal" - }] - } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json deleted file mode 100644 index ff33b59cff9..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [{ - "w": 320, - "h": 250 - }] - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body":{ - "id": "testid", - "imp": [{ - "banner": { - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - - }] - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json deleted file mode 100644 index 3cfb878bd47..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - ] - }, -"expectedMakeRequestsErrors": [ - { - "value": "unexpected end of JSON input", - "comparison": "literal" - }] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json deleted file mode 100644 index b088917afa3..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "", - "placement": "dummyplacement" - } - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "No pubid value provided", - "comparison": "literal" - }] - } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json deleted file mode 100644 index a68db2b823e..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "yu", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "body": { - "seatbid": [] - } - } - } - - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected http status code: 0", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json deleted file mode 100644 index c03aa1b2d89..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "yu", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "status":200, - "body": { - "seatbid": [] - } - } - } - - ], - "expectedMakeBidsErrors": [ - { - "value": "Invalid SeatBids count: 0", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json deleted file mode 100644 index a3e160e4bd8..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - "format": [ - { - "w": 320, - "h": 250 - }, - { - "w": 320, - "h": 300 - } - ], - "w": 320, - "h": 250 - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", - "body": { - "id": "testid", - "imp": [ - { - "banner": { - "format": [ - { - "h": 250, - "w": 320 - }, - { - "h": 300, - "w": 320 - } - ], - "h": 250, - "w": 320 - }, - "id": "testimpid", - "tagid": "dummyplacement" - } - ], - "site": { - "id": "test" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "seatbid": [ - { - "bid": [ - { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "crid": "24080", - "adid": "2068416", - "price": 0.01, - "id": "testid", - "impid": "testimpid", - "cid": "8048" - }, - "type": "banner" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json deleted file mode 100644 index 77228559eee..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "mockBidRequest": { - "id": "testid", - "site": { - "id": "test", - "domain": "test.com" - }, - "imp": [ - { - "id": "testimpid", - "banner": { - - }, - "ext": { - "bidder": { - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" - } - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "Expected at least one banner.format entry or explicit w/h", - "comparison": "literal" - }] -} \ No newline at end of file diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index 6ea76e1283e..63d20d87fc0 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // NoBidAdapter - NoBid Adapter definition diff --git a/adapters/nobid/nobid_test.go b/adapters/nobid/nobid_test.go index a8775b74d18..8b48a303053 100644 --- a/adapters/nobid/nobid_test.go +++ b/adapters/nobid/nobid_test.go @@ -3,9 +3,9 @@ package nobid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/nobid/params_test.go b/adapters/nobid/params_test.go index 75d69943d35..395230df797 100644 --- a/adapters/nobid/params_test.go +++ b/adapters/nobid/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/oms/oms.go b/adapters/oms/oms.go new file mode 100644 index 00000000000..af16cc78ace --- /dev/null +++ b/adapters/oms/oms.go @@ -0,0 +1,80 @@ +package oms + +import ( + "encoding/json" + "fmt" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "net/http" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the OMS adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) == 0 { + bidResponse.Currency = response.Cur + } + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + + return bidResponse, nil +} diff --git a/adapters/oms/oms_test.go b/adapters/oms/oms_test.go new file mode 100644 index 00000000000..9c3c4119f10 --- /dev/null +++ b/adapters/oms/oms_test.go @@ -0,0 +1,20 @@ +package oms + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOms, config.Adapter{ + Endpoint: "http://rt.marphezis.com/pbs"}, config.Server{ExternalUrl: "http://hosturl.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "omstest", bidder) +} diff --git a/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json b/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json new file mode 100755 index 00000000000..576173b548c --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "oms-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "oms-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json b/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json new file mode 100644 index 00000000000..6b292b27a53 --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json @@ -0,0 +1,216 @@ +{ + "mockBidRequest": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + }, + { + "bid": [ + { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-banner-uid.json b/adapters/oms/omstest/exemplary/simple-banner-uid.json new file mode 100755 index 00000000000..077c5562448 --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-banner-uid.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/exemplary/simple-multi-type-banner.json b/adapters/oms/omstest/exemplary/simple-multi-type-banner.json new file mode 100644 index 00000000000..7e96dbfcc7e --- /dev/null +++ b/adapters/oms/omstest/exemplary/simple-multi-type-banner.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/omstest/supplemental/204-response-from-target.json b/adapters/oms/omstest/supplemental/204-response-from-target.json new file mode 100755 index 00000000000..63a0829d9f3 --- /dev/null +++ b/adapters/oms/omstest/supplemental/204-response-from-target.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeBidsErrors": [] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/400-response-from-target.json b/adapters/oms/omstest/supplemental/400-response-from-target.json new file mode 100755 index 00000000000..831788f229d --- /dev/null +++ b/adapters/oms/omstest/supplemental/400-response-from-target.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/500-response-from-target.json b/adapters/oms/omstest/supplemental/500-response-from-target.json new file mode 100755 index 00000000000..9e4e48e2f2b --- /dev/null +++ b/adapters/oms/omstest/supplemental/500-response-from-target.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json b/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json new file mode 100755 index 00000000000..f473ecd6f21 --- /dev/null +++ b/adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rt.marphezis.com/pbs", + "headers": {}, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "oms.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [ + { + "source": "oms.com", + "uids": [ + { + "id": "oms-eid" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pid": "12345" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/oms/params_test.go b/adapters/oms/params_test.go new file mode 100644 index 00000000000..daa78fe7ddf --- /dev/null +++ b/adapters/oms/params_test.go @@ -0,0 +1,56 @@ +package oms + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/oms.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.oms + +// TestValidParams makes sure that the oms schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderOms, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected oms params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the oms schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderOms, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pid": "12345"}`, + `{"pid": "123456"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"pid": "0"}`, +} diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index 6e642ef1979..9036de80ced 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go index 5550f076a99..7a1e539ca29 100644 --- a/adapters/onetag/onetag_test.go +++ b/adapters/onetag/onetag_test.go @@ -3,9 +3,9 @@ package onetag import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/onetag/params_test.go b/adapters/onetag/params_test.go index 4c7326ac9f0..bfc7c6ac27a 100644 --- a/adapters/onetag/params_test.go +++ b/adapters/onetag/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/openweb/openweb.go b/adapters/openweb/openweb.go index 8ef4007f959..45fd2853aec 100644 --- a/adapters/openweb/openweb.go +++ b/adapters/openweb/openweb.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/openweb/openweb_test.go b/adapters/openweb/openweb_test.go index 6332c409623..a63dafd06c2 100644 --- a/adapters/openweb/openweb_test.go +++ b/adapters/openweb/openweb_test.go @@ -3,9 +3,9 @@ package openweb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/openweb/params_test.go b/adapters/openweb/params_test.go index df614df715f..7a53124fa30 100644 --- a/adapters/openweb/params_test.go +++ b/adapters/openweb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/openweb.json diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index bd5e5555a40..f86b7b143fb 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const hbconfig = "hb_pbs_1.0.0" diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index c25bd02e52c..d998148da87 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/openx/params_test.go b/adapters/openx/params_test.go index 94775b57cb0..4e081dba7e5 100644 --- a/adapters/openx/params_test.go +++ b/adapters/openx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/openx.json diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go index 4fad9fe0894..f6af4039fb6 100644 --- a/adapters/operaads/operaads.go +++ b/adapters/operaads/operaads.go @@ -8,11 +8,11 @@ import ( "strings" "text/template" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go index fca277fa937..4e51c8393aa 100644 --- a/adapters/operaads/operaads_test.go +++ b/adapters/operaads/operaads_test.go @@ -3,9 +3,9 @@ package operaads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go index 57a60ce9c53..1da80aa8ce3 100644 --- a/adapters/operaads/params_test.go +++ b/adapters/operaads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/operaads.json diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index 43c3574b4a7..8f1c582afe7 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type OrbidderAdapter struct { @@ -125,19 +125,51 @@ func (rcv OrbidderAdapter) MakeBids(_ *openrtb2.BidRequest, _ *adapters.RequestD return nil, []error{err} } + var bidErrs []error bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) for _, seatBid := range bidResp.SeatBid { - for _, bid := range seatBid.Bid { + for i := range seatBid.Bid { + // later we have to add the bid as a pointer, + // because of this we need a variable that only exists at this loop iteration. + // otherwise there will be issues with multibid and pointer behavior. + bid := seatBid.Bid[i] + bidType, err := getBidType(bid) + if err != nil { + // could not determinate media type, append an error and continue with the next bid. + bidErrs = append(bidErrs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, + BidType: bidType, }) } } if bidResp.Cur != "" { bidResponse.Currency = bidResp.Cur } - return bidResponse, nil + + return bidResponse, bidErrs +} + +func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + + // determinate media type by bid response field mtype + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Could not define media type for impression: %s", bid.ImpID), + } } // Builder builds a new instance of the Orbidder adapter for the given bidder with the given config. diff --git a/adapters/orbidder/orbidder_test.go b/adapters/orbidder/orbidder_test.go index 5a80514ccae..39919da06e0 100644 --- a/adapters/orbidder/orbidder_test.go +++ b/adapters/orbidder/orbidder_test.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/mock" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json b/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json new file mode 100644 index 00000000000..011acb2c7e2 --- /dev/null +++ b/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json @@ -0,0 +1,185 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://test.orbidder.de" + }, + "user": { + "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1" + }, + "imp": [ + { + "id": "multi-format-test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement-1", + "bidfloor": 0.1 + } + } + },{ + "id": "multi-format-test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement-2", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "site": { + "page": "https://test.orbidder.de" + }, + "user": { + "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1" + }, + "imp": [ + { + "id": "multi-format-test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement-1", + "bidfloor": 0.1 + } + } + },{ + "id": "multi-format-test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement-2", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e900", + "impid": "multi-format-test-imp-id-1", + "adid": "11110126", + "price": 0.600000, + "adm": "banner-some-test-ad", + "crid": "banner-test-crid", + "h": 250, + "w": 300, + "mtype": 1 + },{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "multi-format-test-imp-id-2", + "adid": "11110136", + "price": 0.500000, + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}", + "crid": "native-test-crid", + "mtype": 4 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e900", + "impid": "multi-format-test-imp-id-1", + "adid": "11110126", + "price": 0.6, + "adm": "banner-some-test-ad", + "crid": "banner-test-crid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + },{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "multi-format-test-imp-id-2", + "adid": "11110136", + "price": 0.5, + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}", + "crid": "native-test-crid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json index 2315a52c130..80b3c794c5a 100644 --- a/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json +++ b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json @@ -79,7 +79,8 @@ "adm": "some-test-ad", "crid": "test-crid", "h": 250, - "w": 300 + "w": 300, + "mtype": 1 } ] } @@ -102,7 +103,8 @@ "adm": "some-test-ad", "crid": "test-crid", "w": 300, - "h": 250 + "h": 250, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json b/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..9e1181a590b --- /dev/null +++ b/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://test.orbidder.de" + }, + "user": { + "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1" + }, + "imp": [ + { + "id": "web-test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "site": { + "page": "https://test.orbidder.de" + }, + "user": { + "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1" + }, + "imp": [ + { + "id": "web-test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "web-test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "web-test-request-id", + "seatbid": [ + { + "seat": "web-seat-id", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "web-test-imp-id", + "adid": "11110126", + "price": 0.600000, + "adm": "web-some-test-ad", + "crid": "web-test-crid", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "web-test-imp-id", + "adid": "11110126", + "price": 0.6, + "adm": "web-some-test-ad", + "crid": "web-test-crid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json b/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json new file mode 100644 index 00000000000..f4b8ec2c7db --- /dev/null +++ b/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "test.orbidder.de" + }, + "user": { + "buyeruid": "test-user" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://orbidder-test", + "body": { + "id": "test-request-id", + "site": { + "page": "test.orbidder.de" + }, + "user": { + "buyeruid": "test-user" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver":"1.2", + "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]" + }, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "accountId": "orbidder-test", + "placementId": "test-placement", + "bidfloor": 0.1 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}", + "crid": "test-crid", + "mtype": 4 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}", + "crid": "test-crid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json index 1ea133ab1d3..5b4a523f261 100644 --- a/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json +++ b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json @@ -85,7 +85,8 @@ "adm": "some-test-ad", "crid": "test-crid", "h": 250, - "w": 300 + "w": 300, + "mtype": 1 } ] } @@ -108,7 +109,8 @@ "adm": "some-test-ad", "crid": "test-crid", "w": 300, - "h": 250 + "h": 250, + "mtype": 1 }, "type": "banner" } diff --git a/adapters/orbidder/params_test.go b/adapters/orbidder/params_test.go index 2e130f7a9bd..cd95e222aee 100644 --- a/adapters/orbidder/params_test.go +++ b/adapters/orbidder/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/orbidder.json diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index a7d727a57c6..1d07c082507 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/native1" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/outbrain/outbrain_test.go b/adapters/outbrain/outbrain_test.go index 9e667dae83a..4a42679f660 100644 --- a/adapters/outbrain/outbrain_test.go +++ b/adapters/outbrain/outbrain_test.go @@ -3,9 +3,9 @@ package outbrain import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/outbrain/params_test.go b/adapters/outbrain/params_test.go index a8d81d6234d..666724cd6eb 100644 --- a/adapters/outbrain/params_test.go +++ b/adapters/outbrain/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/ownadx/ownadx.go b/adapters/ownadx/ownadx.go new file mode 100644 index 00000000000..77baa63b4cd --- /dev/null +++ b/adapters/ownadx/ownadx.go @@ -0,0 +1,214 @@ +package ownadx + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func (adapter *adapter) getRequestData(bidRequest *openrtb2.BidRequest, impExt *openrtb_ext.ExtImpOwnAdx, imps []openrtb2.Imp) (*adapters.RequestData, error) { + pbidRequest := createBidRequest(bidRequest, imps) + reqJSON, err := json.Marshal(pbidRequest) + if err != nil { + return nil, &errortypes.BadInput{ + Message: "Prebid bidder request not valid or can't be marshalled. Err: " + err.Error(), + } + } + url, err := adapter.buildEndpointURL(impExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while creating endpoint. Err: " + err.Error(), + } + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + return &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: headers}, nil + +} +func createBidRequest(rtbBidRequest *openrtb2.BidRequest, imps []openrtb2.Imp) *openrtb2.BidRequest { + bidRequest := *rtbBidRequest + bidRequest.Imp = imps + return &bidRequest +} +func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpOwnAdx) (string, error) { + endpointParams := macros.EndpointTemplateParams{ + ZoneID: params.SspId, + AccountID: params.SeatId, + SourceId: params.TokenId, + } + return macros.ResolveMacros(adapter.endpoint, endpointParams) +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpOwnAdx, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Bidder extension not valid or can't be unmarshalled", + } + } + + var ownAdxExt openrtb_ext.ExtImpOwnAdx + if err := json.Unmarshal(bidderExt.Bidder, &ownAdxExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + + return &ownAdxExt, nil +} + +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + errs := make([]error, 0, len(request.Imp)) + if len(request.Imp) == 0 { + errs = append(errs, &errortypes.BadInput{ + Message: "No impression in the bid request"}, + ) + return nil, errs + } + extImps, errors := groupImpsByExt(request.Imp) + if len(errors) != 0 { + errs = append(errs, errors...) + } + if len(extImps) == 0 { + return nil, errs + } + reqDetail := make([]*adapters.RequestData, 0, len(extImps)) + for k, imps := range extImps { + bidRequest, err := adapter.getRequestData(request, &k, imps) + if err != nil { + errs = append(errs, err) + } else { + reqDetail = append(reqDetail, bidRequest) + } + } + return reqDetail, errs +} +func groupImpsByExt(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpOwnAdx][]openrtb2.Imp, []error) { + respExt := make(map[openrtb_ext.ExtImpOwnAdx][]openrtb2.Imp) + errors := make([]error, 0, len(imps)) + for _, imp := range imps { + ownAdxExt, err := getImpressionExt(&(imp)) + if err != nil { + errors = append(errors, err) + continue + } + + respExt[*ownAdxExt] = append(respExt[*ownAdxExt], imp) + } + return respExt, errors +} + +func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode == http.StatusBadRequest { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad request: %d", response.StatusCode), + }, + } + } + if response.StatusCode != http.StatusOK { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.test = 1 for more info.", response.StatusCode), + }, + } + } + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response "), + }, + } + } + if len(bidResp.SeatBid) == 0 { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Array SeatBid cannot be empty "), + }, + } + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + if len(seatBid.Bid) == 0 { + return nil, []error{ + &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bid cannot be empty "), + }, + } + } + for i := 0; i < len(seatBid.Bid); i++ { + var bidType openrtb_ext.BidType + bid := seatBid.Bid[i] + + bidType, err := getMediaType(bid) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bid type is invalid", + }} + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + + return bidResponse, nil +} + +// Builder builds a new instance of the OwnAdx adapter for the given bidder with the given config +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + + return bidder, nil +} + +func getMediaType(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("invalid BidType: %d", bid.MType) + } +} diff --git a/adapters/ownadx/ownadx_test.go b/adapters/ownadx/ownadx_test.go new file mode 100644 index 00000000000..07dc928b9b0 --- /dev/null +++ b/adapters/ownadx/ownadx_test.go @@ -0,0 +1,18 @@ +package ownadx + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOwnAdx, config.Adapter{ + Endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.AccountID}}/{{.ZoneID}}?token={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "ownadxtest", bidder) +} diff --git a/adapters/ownadx/ownadxtest/exemplary/banner.json b/adapters/ownadx/ownadxtest/exemplary/banner.json new file mode 100644 index 00000000000..f77321c53b1 --- /dev/null +++ b/adapters/ownadx/ownadxtest/exemplary/banner.json @@ -0,0 +1,166 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "mtype": 1, + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "mtype": 1, + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/exemplary/video.json b/adapters/ownadx/ownadxtest/exemplary/video.json new file mode 100644 index 00000000000..7cac425c7e3 --- /dev/null +++ b/adapters/ownadx/ownadxtest/exemplary/video.json @@ -0,0 +1,196 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2 + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/supplemental/bad-server-response.json b/adapters/ownadx/ownadxtest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..61b6da04fa8 --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/bad-server-response.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200 + + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response ", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/supplemental/bid-empty-.json b/adapters/ownadx/ownadxtest/supplemental/bid-empty-.json new file mode 100644 index 00000000000..8d40e878925 --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/bid-empty-.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "seat": "seat" + } + ], + "cur": "USD" + } + + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid cannot be empty ", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/supplemental/bidext-type.json b/adapters/ownadx/ownadxtest/supplemental/bidext-type.json new file mode 100644 index 00000000000..b1d634287d7 --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/bidext-type.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "seatbid": [ + { + "bid": [ + { + "id": "id", + "impid": "id", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "Test", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB1" + ], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid type is invalid", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/kubient/kubienttest/supplemental/status_204.json b/adapters/ownadx/ownadxtest/supplemental/http-status-204.json similarity index 60% rename from adapters/kubient/kubienttest/supplemental/status_204.json rename to adapters/ownadx/ownadxtest/supplemental/http-status-204.json index 6794d58be6c..4b7f8663f02 100644 --- a/adapters/kubient/kubienttest/supplemental/status_204.json +++ b/adapters/ownadx/ownadxtest/supplemental/http-status-204.json @@ -5,42 +5,37 @@ { "id": "test-imp-id", "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] + "w": 300, + "h": 250 }, "ext": { "bidder": { - "zoneid": "203" + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" } } } ] }, - "httpCalls": [ { "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", "body": { "id": "test-request-id", "imp": [ { "id": "test-imp-id", "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] + "w": 300, + "h": 250 }, "ext": { "bidder": { - "zoneid": "203" + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" } } } @@ -48,11 +43,9 @@ } }, "mockResponse": { - "status": 204, - "body": {} + "status": 204 } } ], - "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json b/adapters/ownadx/ownadxtest/supplemental/http-status-400.json similarity index 59% rename from adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json rename to adapters/ownadx/ownadxtest/supplemental/http-status-400.json index b7ed65da2af..035bc323e38 100644 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_418.json +++ b/adapters/ownadx/ownadxtest/supplemental/http-status-400.json @@ -5,42 +5,37 @@ { "id": "test-imp-id", "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] + "w": 300, + "h": 250 }, "ext": { "bidder": { - "pid": "123" + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" } } } ] }, - "httpCalls": [ { "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", "body": { "id": "test-request-id", "imp": [ { "id": "test-imp-id", "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] + "w": 300, + "h": 250 }, "ext": { "bidder": { - "pid": "123" + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" } } } @@ -48,16 +43,15 @@ } }, "mockResponse": { - "status": 418, - "body": {} + "status": 400 } } ], - "expectedMakeBidsErrors": [ { - "value": "unexpected HTTP status 418.", + "value": "Bad request: 400", "comparison": "literal" } - ] -} + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/supplemental/invalid-req-empty-imp.json b/adapters/ownadx/ownadxtest/supplemental/invalid-req-empty-imp.json new file mode 100644 index 00000000000..0407ed6cfbf --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/invalid-req-empty-imp.json @@ -0,0 +1,34 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "id", + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [] +} diff --git a/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp-ext.json b/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp-ext.json new file mode 100644 index 00000000000..9b8ba0d1820 --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp-ext.json @@ -0,0 +1,47 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Bidder extension not valid or can't be unmarshalled", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + } + + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [] +} diff --git a/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp.ext.bidder.json b/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp.ext.bidder.json new file mode 100644 index 00000000000..b04e037d0a2 --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/invalid-req-imp.ext.bidder.json @@ -0,0 +1,49 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Error while unmarshaling bidder extension", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": [] + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [] +} diff --git a/adapters/ownadx/ownadxtest/supplemental/seatbid-empty-.json b/adapters/ownadx/ownadxtest/supplemental/seatbid-empty-.json new file mode 100644 index 00000000000..f49ece3ea2f --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/seatbid-empty-.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "id", + "bidid": "id", + "cur": "USD" + } + + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Array SeatBid cannot be empty ", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/ownadx/ownadxtest/supplemental/unexpected-status.json b/adapters/ownadx/ownadxtest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..f501a11064e --- /dev/null +++ b/adapters/ownadx/ownadxtest/supplemental/unexpected-status.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.prebid-ownadx.com/bidder/bid/2/11?token=126151698247", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tokenId": "126151698247", + "sspId": "11", + "seatId": "2" + } + } + } + ], + "device": { + "ua": "UA", + "ip": "123.3.4.123" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "userid" + }, + "site": { + "id": "id", + "domain": "test,com", + "cat": [ + "IAB12" + ], + "publisher": { + "id": "pubid" + } + } + } + }, + "mockResponse": { + "status": 403 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.test = 1 for more info.", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index fcdc0264935..7c18a840025 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/pangle/pangle_test.go b/adapters/pangle/pangle_test.go index 3653a60c81c..243bbadc90a 100644 --- a/adapters/pangle/pangle_test.go +++ b/adapters/pangle/pangle_test.go @@ -3,9 +3,9 @@ package pangle import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pangle/param_test.go b/adapters/pangle/param_test.go index 5e1d30b3c7b..e25b7d740c4 100644 --- a/adapters/pangle/param_test.go +++ b/adapters/pangle/param_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/pgamssp/params_test.go b/adapters/pgamssp/params_test.go new file mode 100644 index 00000000000..d2f06cbcfa8 --- /dev/null +++ b/adapters/pgamssp/params_test.go @@ -0,0 +1,47 @@ +package pgamssp + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderPGAMSsp, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderPGAMSsp, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, +} diff --git a/adapters/pgamssp/pgamssp.go b/adapters/pgamssp/pgamssp.go new file mode 100644 index 00000000000..3e315fedc95 --- /dev/null +++ b/adapters/pgamssp/pgamssp.go @@ -0,0 +1,145 @@ +package pgamssp + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +type reqBodyExt struct { + PGAMSspBidderExt reqBodyExtBidder `json:"bidder"` +} + +type reqBodyExtBidder struct { + Type string `json:"type"` + PlacementID string `json:"placementId,omitempty"` + EndpointID string `json:"endpointId,omitempty"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var err error + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb2.Imp{imp} + + var bidderExt adapters.ExtImpBidder + var pgamExt openrtb_ext.ImpExtPgamSsp + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{err} + } + if err = json.Unmarshal(bidderExt.Bidder, &pgamExt); err != nil { + return nil, []error{err} + } + + temp := reqBodyExt{PGAMSspBidderExt: reqBodyExtBidder{}} + + if pgamExt.PlacementID != "" { + temp.PGAMSspBidderExt.PlacementID = pgamExt.PlacementID + temp.PGAMSspBidderExt.Type = "publisher" + } else if pgamExt.EndpointID != "" { + temp.PGAMSspBidderExt.EndpointID = pgamExt.EndpointID + temp.PGAMSspBidderExt.Type = "network" + } + + finalyImpExt, err := json.Marshal(temp) + if err != nil { + return nil, []error{err} + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, err := a.makeRequest(&reqCopy) + if err != nil { + return nil, []error{err} + } + + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + } + return adapterRequests, nil +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, err +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getBidMediaType(&seatBid.Bid[i]) + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) + } +} diff --git a/adapters/pgamssp/pgamssp_test.go b/adapters/pgamssp/pgamssp_test.go new file mode 100644 index 00000000000..10e72f5d093 --- /dev/null +++ b/adapters/pgamssp/pgamssp_test.go @@ -0,0 +1,20 @@ +package pgamssp + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderPGAMSsp, config.Adapter{ + Endpoint: "http://test.com/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "pgamssptest", bidder) +} diff --git a/adapters/pgamssp/pgamssptest/exemplary/endpointId.json b/adapters/pgamssp/pgamssptest/exemplary/endpointId.json new file mode 100644 index 00000000000..72805db16ba --- /dev/null +++ b/adapters/pgamssp/pgamssptest/exemplary/endpointId.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pgamssp/pgamssptest/exemplary/simple-banner.json b/adapters/pgamssp/pgamssptest/exemplary/simple-banner.json new file mode 100644 index 00000000000..3850c4492c2 --- /dev/null +++ b/adapters/pgamssp/pgamssptest/exemplary/simple-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pgamssp/pgamssptest/exemplary/simple-native.json b/adapters/pgamssp/pgamssptest/exemplary/simple-native.json new file mode 100644 index 00000000000..8796adacd8e --- /dev/null +++ b/adapters/pgamssp/pgamssptest/exemplary/simple-native.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pgamssp/pgamssptest/exemplary/simple-video.json b/adapters/pgamssp/pgamssptest/exemplary/simple-video.json new file mode 100644 index 00000000000..a39d0111694 --- /dev/null +++ b/adapters/pgamssp/pgamssptest/exemplary/simple-video.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pgamssp/pgamssptest/exemplary/simple-web-banner.json b/adapters/pgamssp/pgamssptest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..fca8ce176ea --- /dev/null +++ b/adapters/pgamssp/pgamssptest/exemplary/simple-web-banner.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pgamssp/pgamssptest/supplemental/bad_media_type.json b/adapters/pgamssp/pgamssptest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..6f016367b33 --- /dev/null +++ b/adapters/pgamssp/pgamssptest/supplemental/bad_media_type.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": {} + } + ], + "seat": "pgamssp" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unable to fetch mediaType in multi-format: test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/pgamssp/pgamssptest/supplemental/bad_response.json b/adapters/pgamssp/pgamssptest/supplemental/bad_response.json new file mode 100644 index 00000000000..8c9f6f523fe --- /dev/null +++ b/adapters/pgamssp/pgamssptest/supplemental/bad_response.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/pgamssp/pgamssptest/supplemental/status-204.json b/adapters/pgamssp/pgamssptest/supplemental/status-204.json new file mode 100644 index 00000000000..3869591eb40 --- /dev/null +++ b/adapters/pgamssp/pgamssptest/supplemental/status-204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [] +} diff --git a/adapters/pgamssp/pgamssptest/supplemental/status-not-200.json b/adapters/pgamssp/pgamssptest/supplemental/status-not-200.json new file mode 100644 index 00000000000..0c9b3eec08b --- /dev/null +++ b/adapters/pgamssp/pgamssptest/supplemental/status-not-200.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://test.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "type": "publisher" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/pubmatic/params_test.go b/adapters/pubmatic/params_test.go index a5a7773f7af..d5d1d46842f 100644 --- a/adapters/pubmatic/params_test.go +++ b/adapters/pubmatic/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/pubmatic.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 8084b2bcabe..7ab0248b937 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -10,18 +10,20 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const MAX_IMPRESSIONS_PUBMATIC = 30 +const ae = "ae" + type PubmaticAdapter struct { - URI string + URI string + bidderName string } type pubmaticBidExt struct { @@ -43,6 +45,7 @@ type pubmaticBidExtVideo struct { type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data json.RawMessage `json:"data,omitempty"` + AE int `json:"ae,omitempty"` } type ExtAdServer struct { @@ -61,6 +64,10 @@ type extRequestAdServer struct { openrtb_ext.ExtRequest } +type respExt struct { + FledgeAuctionConfigs map[string]json.RawMessage `json:"fledge_auction_configs,omitempty"` +} + const ( dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" @@ -305,6 +312,10 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP populateFirstPartyDataImpAttributes(bidderExt.Data, extMap) } + if bidderExt.AE != 0 { + extMap[ae] = bidderExt.AE + } + imp.Ext = nil if len(extMap) > 0 { ext, err := json.Marshal(extMap) @@ -390,7 +401,6 @@ func getAlternateBidderCodesFromRequestExt(reqExt *openrtb_ext.ExtRequest) []str func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { for _, keyVal := range keywords { if len(keyVal.Values) == 0 { - logf("No values present for key = %s", keyVal.Key) continue } else { key := keyVal.Key @@ -467,6 +477,20 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa if bidResp.Cur != "" { bidResponse.Currency = bidResp.Cur } + + if bidResp.Ext != nil { + var bidRespExt respExt + if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil { + bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs)) + for impId, config := range bidRespExt.FledgeAuctionConfigs { + fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{ + ImpId: impId, + Config: config, + } + bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig) + } + } + } return bidResponse, errs } @@ -614,16 +638,11 @@ func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType { return bidType } -func logf(msg string, args ...interface{}) { - if glog.V(2) { - glog.Infof(msg, args...) - } -} - // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ - URI: config.Endpoint, + URI: config.Endpoint, + bidderName: string(bidderName), } return bidder, nil } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 7553519f990..b40bf2d4fb2 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -8,10 +8,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/pubmatic/pubmatictest/exemplary/fledge.json b/adapters/pubmatic/pubmatictest/exemplary/fledge.json new file mode 100644 index 00000000000..793f0984624 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/exemplary/fledge.json @@ -0,0 +1,166 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "ae": 1, + "bidder": { + "publisherId": "999", + "adSlot": "AdTag_Div1@300x250" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ], + "h": 250, + "w": 300 + }, + "tagid": "AdTag_Div1", + "ext": { + "ae": 1 + } + } + ], + "ext": {"prebid":{}} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "pubmatic", + "bid": [ + { + "id": "9d8e77c2-ee0f-4781-af59-5359493b11af", + "impid": "test-imp-id", + "price": 1.1, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 90, + "w": 728, + "ext": {} + } + ] + } + ], + "cur": "USD", + "ext": { + "fledge_auction_configs": { + "test-imp-id": { + "seller": "PUBMATIC_SELLER_CONSTANT_STRING", + "sellerTimeout": 123, + "decisionLogicUrl": "PUBMATIC_URL_CONSTANT_STRING", + "interestGroupBuyers": [ + "somedomain1.com", + "somedomain2.com", + "somedomain3.com", + "somedomain4.com" + ], + "perBuyerSignals": { + "somedomain1.com": { + "multiplier": 1, + "win_reporting_id": "1234" + }, + "somedomain2.com": { + "multiplier": 2, + "win_reporting_id": "2345" + }, + "somedomain3.com": { + "multiplier": 3, + "win_reporting_id": "3456" + }, + "somedomain4.com": { + "multiplier": 4, + "win_reporting_id": "4567" + } + } + } + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "9d8e77c2-ee0f-4781-af59-5359493b11af", + "impid": "test-imp-id", + "price": 1.1, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 728, + "h": 90, + "ext": {} + }, + "type": "banner" + } + ], + "fledgeauctionconfigs": [ + { + "impid": "test-imp-id", + "config": { + "seller": "PUBMATIC_SELLER_CONSTANT_STRING", + "sellerTimeout": 123, + "decisionLogicUrl": "PUBMATIC_URL_CONSTANT_STRING", + "interestGroupBuyers": [ + "somedomain1.com", + "somedomain2.com", + "somedomain3.com", + "somedomain4.com" + ], + "perBuyerSignals": { + "somedomain1.com": { + "multiplier": 1, + "win_reporting_id": "1234" + }, + "somedomain2.com": { + "multiplier": 2, + "win_reporting_id": "2345" + }, + "somedomain3.com": { + "multiplier": 3, + "win_reporting_id": "3456" + }, + "somedomain4.com": { + "multiplier": 4, + "win_reporting_id": "4567" + } + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 35265c8a4c3..da3cab34dae 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -8,10 +8,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type PubnativeAdapter struct { diff --git a/adapters/pubnative/pubnative_test.go b/adapters/pubnative/pubnative_test.go index 338575a51b0..b1b1bf85e05 100644 --- a/adapters/pubnative/pubnative_test.go +++ b/adapters/pubnative/pubnative_test.go @@ -3,9 +3,9 @@ package pubnative import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go index ac2b314b96f..4b3c6c017db 100644 --- a/adapters/pulsepoint/params_test.go +++ b/adapters/pulsepoint/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 6c17a6c30f4..f65e010341e 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -6,10 +6,10 @@ import ( "net/http" "strconv" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index d30f49cabcf..20f73c89ecb 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -3,9 +3,9 @@ package pulsepoint import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/pwbid/params_test.go b/adapters/pwbid/params_test.go index e16fd13c4dc..44d36379cca 100644 --- a/adapters/pwbid/params_test.go +++ b/adapters/pwbid/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pwbid/pwbid.go b/adapters/pwbid/pwbid.go index 21b564ff124..2452c08a863 100644 --- a/adapters/pwbid/pwbid.go +++ b/adapters/pwbid/pwbid.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/pwbid/pwbid_test.go b/adapters/pwbid/pwbid_test.go index 21a4194249e..9abad59d97c 100644 --- a/adapters/pwbid/pwbid_test.go +++ b/adapters/pwbid/pwbid_test.go @@ -3,14 +3,14 @@ package pwbid import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderPWBid, config.Adapter{ - Endpoint: "https://bid.pubwise.io/prebid"}, + Endpoint: "https://bidder.east2.pubwise.io/bid/pubwisedirect"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 842, DataCenter: "2"}) if buildErr != nil { diff --git a/adapters/pwbid/pwbidtest/exemplary/banner.json b/adapters/pwbid/pwbidtest/exemplary/banner.json index ba618cb8cf1..4cf93e1ab76 100644 --- a/adapters/pwbid/pwbidtest/exemplary/banner.json +++ b/adapters/pwbid/pwbidtest/exemplary/banner.json @@ -23,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id-banner", "imp": [ @@ -90,4 +90,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/pwbid/pwbidtest/exemplary/native.json b/adapters/pwbid/pwbidtest/exemplary/native.json index 907c16d467a..ff57752c5ea 100644 --- a/adapters/pwbid/pwbidtest/exemplary/native.json +++ b/adapters/pwbid/pwbidtest/exemplary/native.json @@ -18,7 +18,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id-native", "imp": [ @@ -78,4 +78,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/pwbid/pwbidtest/exemplary/optional-params.json b/adapters/pwbid/pwbidtest/exemplary/optional-params.json index a080be90208..5ababb24bdc 100644 --- a/adapters/pwbid/pwbidtest/exemplary/optional-params.json +++ b/adapters/pwbid/pwbidtest/exemplary/optional-params.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id-banner", "imp": [ @@ -94,4 +94,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/pwbid/pwbidtest/exemplary/video.json b/adapters/pwbid/pwbidtest/exemplary/video.json index b74c780d0a9..6257de632d4 100644 --- a/adapters/pwbid/pwbidtest/exemplary/video.json +++ b/adapters/pwbid/pwbidtest/exemplary/video.json @@ -20,7 +20,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json index 146ba93a27d..0d469893e0c 100644 --- a/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json +++ b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/pwbid/pwbidtest/supplemental/response-204.json b/adapters/pwbid/pwbidtest/supplemental/response-204.json index 5fff7ee32cc..4fc8961e0bb 100644 --- a/adapters/pwbid/pwbidtest/supplemental/response-204.json +++ b/adapters/pwbid/pwbidtest/supplemental/response-204.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/pwbid/pwbidtest/supplemental/response-400.json b/adapters/pwbid/pwbidtest/supplemental/response-400.json index d594e571243..a1517883243 100644 --- a/adapters/pwbid/pwbidtest/supplemental/response-400.json +++ b/adapters/pwbid/pwbidtest/supplemental/response-400.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/pwbid/pwbidtest/supplemental/response-500.json b/adapters/pwbid/pwbidtest/supplemental/response-500.json index fa3d4d063a8..c2b5649418b 100644 --- a/adapters/pwbid/pwbidtest/supplemental/response-500.json +++ b/adapters/pwbid/pwbidtest/supplemental/response-500.json @@ -19,7 +19,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bid.pubwise.io/prebid", + "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect", "body": { "id": "test-request-id", "imp": [ diff --git a/adapters/relevantdigital/params_test.go b/adapters/relevantdigital/params_test.go new file mode 100644 index 00000000000..cd889a5eaa8 --- /dev/null +++ b/adapters/relevantdigital/params_test.go @@ -0,0 +1,42 @@ +package relevantdigital + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderRelevantDigital, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderRelevantDigital, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"accountId": "5fcf49f83a64ba6602b5be7e", "placementId" : "63b68275b4f35962c8eec9b1_5fcf49f83a64ba6602b5be9a", "pbsHost" : "some-host" }`, +} + +var invalidParams = []string{ + `{"accountId": 123, "placementId" : 123, "pbsHost" : ""}`, +} diff --git a/adapters/relevantdigital/relevantdigital.go b/adapters/relevantdigital/relevantdigital.go new file mode 100644 index 00000000000..e8b1df8c100 --- /dev/null +++ b/adapters/relevantdigital/relevantdigital.go @@ -0,0 +1,330 @@ +package relevantdigital + +import ( + "encoding/json" + "fmt" + "math" + "net/http" + "strings" + "text/template" + + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" + jsonpatch "gopkg.in/evanphx/json-patch.v4" +) + +type adapter struct { + endpoint *template.Template + name string +} + +const relevant_domain = ".relevant-digital.com" +const default_timeout = 1000 +const default_bufffer_ms = 250 + +type prebidExt struct { + StoredRequest struct { + Id string `json:"id"` + } `json:"storedrequest"` + Debug bool `json:"debug"` +} + +type relevantExt struct { + Relevant struct { + Count int `json:"count"` + AdapterType string `json:"adapterType"` + } `json:"relevant"` + Prebid prebidExt `json:"prebid"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + return &adapter{ + endpoint: template, + name: bidderName.String(), + }, nil +} + +func patchBidRequestExt(prebidBidRequest *openrtb2.BidRequest, id string) error { + var bidRequestExt relevantExt + if len(prebidBidRequest.Ext) != 0 { + if err := json.Unmarshal(prebidBidRequest.Ext, &bidRequestExt); err != nil { + return &errortypes.FailedToRequestBids{ + Message: fmt.Sprintf("failed to unmarshal ext, %s", prebidBidRequest.Ext), + } + } + } + + count := bidRequestExt.Relevant.Count + if bidRequestExt.Relevant.Count >= 5 { + return &errortypes.FailedToRequestBids{ + Message: "too many requests", + } + } else { + count = count + 1 + } + + bidRequestExt.Relevant.Count = count + bidRequestExt.Relevant.AdapterType = "server" + bidRequestExt.Prebid.StoredRequest.Id = id + + ext, err := json.Marshal(bidRequestExt) + if err != nil { + return &errortypes.FailedToRequestBids{ + Message: "failed to marshal", + } + } + + if len(prebidBidRequest.Ext) == 0 { + prebidBidRequest.Ext = ext + return nil + } + + patchedExt, err := jsonpatch.MergePatch(prebidBidRequest.Ext, ext) + if err != nil { + return &errortypes.FailedToRequestBids{ + Message: fmt.Sprintf("failed patch ext, %s", err), + } + } + prebidBidRequest.Ext = patchedExt + return nil +} + +func patchBidImpExt(imp *openrtb2.Imp, id string) { + imp.Ext = []byte(fmt.Sprintf("{\"prebid\":{\"storedrequest\":{\"id\":\"%s\"}}}", id)) +} + +func setTMax(prebidBidRequest *openrtb2.BidRequest, pbsBufferMs int) { + timeout := float64(prebidBidRequest.TMax) + if timeout <= 0 { + timeout = default_timeout + } + buffer := float64(pbsBufferMs) + prebidBidRequest.TMax = int64(math.Min(math.Max(timeout-buffer, buffer), timeout)) +} + +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params []*openrtb_ext.ExtRelevantDigital) ([]byte, error) { + bidRequestCopy := *prebidBidRequest + + err := patchBidRequestExt(&bidRequestCopy, params[0].AccountId) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("failed to create bidRequest, error: %s", err), + } + } + + setTMax(&bidRequestCopy, params[0].PbsBufferMs) + + for idx := range bidRequestCopy.Imp { + patchBidImpExt(&bidRequestCopy.Imp[idx], params[idx].PlacementId) + } + + return createJSONRequest(&bidRequestCopy) +} + +func createJSONRequest(bidRequest *openrtb2.BidRequest) ([]byte, error) { + reqJSON, err := json.Marshal(bidRequest) + if err != nil { + return nil, err + } + + // Scrub previous ext data from relevant, if any + // imp[].ext.context.relevant + // imp[].[banner/native/video/audio].ext.relevant + impKeyTypes := []string{"banner", "video", "native", "audio"} + for idx := range bidRequest.Imp { + for _, key := range impKeyTypes { + reqJSON = jsonparser.Delete(reqJSON, "imp", fmt.Sprintf("[%d]", idx), key, "ext", "relevant") + } + reqJSON = jsonparser.Delete(reqJSON, "imp", fmt.Sprintf("[%d]", idx), "ext", "context", "relevant") + } + + // Scrub previous prebid data (to not set cache on wrong servers) + // ext.prebid.[cache/targeting/aliases] + prebidKeyTypes := []string{"cache", "targeting", "aliases"} + for _, key := range prebidKeyTypes { + reqJSON = jsonparser.Delete(reqJSON, "ext", "prebid", key) + } + return reqJSON, nil +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtRelevantDigital, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "imp.ext not provided", + } + } + relevantExt := openrtb_ext.ExtRelevantDigital{PbsBufferMs: default_bufffer_ms} + if err := json.Unmarshal(bidderExt.Bidder, &relevantExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + return &relevantExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtRelevantDigital) (string, error) { + params.Host = strings.ReplaceAll(params.Host, "http://", "") + params.Host = strings.ReplaceAll(params.Host, "https://", "") + params.Host = strings.ReplaceAll(params.Host, relevant_domain, "") + + endpointParams := macros.EndpointTemplateParams{Host: params.Host} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params []*openrtb_ext.ExtRelevantDigital) (*adapters.RequestData, error) { + reqJSON, err := createBidRequest(prebidBidRequest, params) + + if err != nil { + return nil, err + } + + url, err := a.buildEndpointURL(params[0]) + if err != nil { + return nil, err + } + + return &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: getHeaders(prebidBidRequest), + }, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impParams, errs := getImpressionsInfo(request.Imp) + if len(errs) > 0 { + return nil, errs + } + + bidRequest, err := a.buildAdapterRequest(request, impParams) + if err != nil { + errs = []error{err} + } + + if bidRequest != nil { + return []*adapters.RequestData{bidRequest}, errs + } + return nil, errs +} + +func getImpressionsInfo(imps []openrtb2.Imp) (resImps []*openrtb_ext.ExtRelevantDigital, errors []error) { + for _, imp := range imps { + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + resImps = append(resImps, impExt) + } + return +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + return headers +} + +func getMediaTypeForBidFromExt(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) + } + } + return "", fmt.Errorf("failed to parse bid type, missing ext: %s", bid.ImpID) +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return getMediaTypeForBidFromExt(bid) + } +} + +func isSupportedMediaType(bidType openrtb_ext.BidType) error { + switch bidType { + case openrtb_ext.BidTypeBanner: + fallthrough + case openrtb_ext.BidTypeVideo: + fallthrough + case openrtb_ext.BidTypeAudio: + fallthrough + case openrtb_ext.BidTypeNative: + return nil + } + return fmt.Errorf("bid type not supported %s", bidType) +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(response.SeatBid)) + bidResponse.Currency = response.Cur + var errs []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + + if err != nil { + errs = append(errs, err) + continue + } + if err := isSupportedMediaType(bidType); err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} diff --git a/adapters/relevantdigital/relevantdigital_test.go b/adapters/relevantdigital/relevantdigital_test.go new file mode 100644 index 00000000000..5c627ca7c65 --- /dev/null +++ b/adapters/relevantdigital/relevantdigital_test.go @@ -0,0 +1,28 @@ +package relevantdigital + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRelevantDigital, config.Adapter{ + Endpoint: "https://{{.Host}}.relevant-digital.com/openrtb2/auction"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "relevantdigitaltest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAceex, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-audio.json b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-audio.json new file mode 100644 index 00000000000..2a7e6189154 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-audio.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-audio-id", + "audio": { + "mimes": [ + "audio/mp4" + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-audio-id", + "audio": { + "mimes": [ + "audio/mp4" + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [ + { + "id": "9244e776-e0c7-4d76-8c20-88971909114b", + "impid": "test-audio-id", + "price": 2.50, + "adm": "a creative", + "adomain": [ + "advertiser.com" + ], + "crid": "9999", + "mtype": 3, + "ext": { + "prebid": { + "type": "audio" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "9244e776-e0c7-4d76-8c20-88971909114b", + "impid": "test-audio-id", + "price": 2.50, + "adm": "a creative", + "adomain": [ + "advertiser.com" + ], + "crid": "9999", + "mtype": 3, + "ext": { + "prebid": { + "type": "audio" + } + } + }, + "type": "audio" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-banner.json b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-banner.json new file mode 100644 index 00000000000..ea30dfe3177 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-banner.json @@ -0,0 +1,206 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ], + "site": { + "domain": "somedomain.com", + "page": "https://somedomain.com", + "publisher": { + "id": "1001", + "domain": "somepub.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + }, + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "site": { + "domain": "somedomain.com", + "page": "https://somedomain.com", + "publisher": { + "id": "1001", + "domain": "somepub.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "ext": { + "prebid": { + "debug" : false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "seatbid": [ + { + "seat": "relevantdigital", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "div-1", + "price": 0.500000, + "adm": "", + "crid": "crid_10", + "w": 728, + "h": 90, + "mtype": 1, + "ext": { + "bidtype": 0, + "dspid": 6, + "origbidcpm": 11.13998874085194, + "origbidcur": "USD", + "prebid": { + "bidid": "09abd496-5706-4311-a356-d559629a1d17", + "events": { + "win": "https://fakeHost.relevant-digital.com/event?t=win\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078", + "imp": "https://fakeHost.relevant-digital.com/event?t=imp\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078" + }, + "meta": { + "adaptercode": "relevantdigital" + }, + "targeting": { + "hb_bidder": "relevantdigital", + "hb_cache_host": "somedomain.com", + "hb_cache_path": "/analytics_cache/read", + "hb_pb": "11.10", + "hb_size": "300x250" + }, + "type": "banner" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "div-1", + "price": 0.5, + "adm": "", + "crid": "crid_10", + "w": 728, + "h": 90, + "mtype": 1, + "ext": { + "bidtype": 0, + "dspid": 6, + "origbidcpm": 11.13998874085194, + "origbidcur": "USD", + "prebid": { + "bidid": "09abd496-5706-4311-a356-d559629a1d17", + "events": { + "win": "https://fakeHost.relevant-digital.com/event?t=win\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078", + "imp": "https://fakeHost.relevant-digital.com/event?t=imp\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078" + }, + "meta": { + "adaptercode": "relevantdigital" + }, + "targeting": { + "hb_bidder": "relevantdigital", + "hb_cache_host": "somedomain.com", + "hb_cache_path": "/analytics_cache/read", + "hb_pb": "11.10", + "hb_size": "300x250" + }, + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-native.json b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-native.json new file mode 100644 index 00000000000..a42a8f3b1b4 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-native.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-native-id", + "native": { + "request": "test-native-request" + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-native-id", + "native": { + "request": "test-native-request" + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [ + { + "id": "9244e776-e0c7-4d76-8c20-88971909114b", + "impid": "test-native-id", + "price": 2.50, + "adm": "a creative", + "adid": "9999", + "adomain": [ + "advertiser.com" + ], + "crid": "9999", + "w": 300, + "h": 600, + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "9244e776-e0c7-4d76-8c20-88971909114b", + "impid": "test-native-id", + "price": 2.50, + "adm": "a creative", + "adid": "9999", + "adomain": [ + "advertiser.com" + ], + "crid": "9999", + "w": 300, + "h": 600, + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-video.json b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-video.json new file mode 100644 index 00000000000..6cc8d25468a --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/exemplary/simple-video.json @@ -0,0 +1,185 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "video": { + "mimes": [ + "video/mp4", + "video/webm", + "video/ogg" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ], + "site": { + "domain": "somedomain.com", + "page": "https://somedomain.com", + "publisher": { + "id": "1001", + "domain": "somepub.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "video": { + "mimes": [ + "video/mp4", + "video/webm", + "video/ogg" + ], + "w": 640, + "h": 480 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "site": { + "domain": "somedomain.com", + "page": "https://somedomain.com", + "publisher": { + "id": "1001", + "domain": "somepub.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245" + }, + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "seatbid": [ + { + "seat": "relevantdigital", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "div-1", + "price": 0.500000, + "adm": " ", + "crid": "crid_10", + "mtype": 2, + "ext": { + "dspid": 6, + "origbidcpm": 11.13998874085194, + "origbidcur": "USD", + "prebid": { + "bidid": "09abd496-5706-4311-a356-d559629a1d17", + "events": { + "win": "https://fakeHost.relevant-digital.com/event?t=win\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078", + "imp": "https://fakeHost.relevant-digital.com/event?t=imp\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078" + }, + "meta": { + "adaptercode": "relevantdigital" + }, + "targeting": { + "hb_bidder": "relevantdigital", + "hb_cache_host": "somedomain.com", + "hb_cache_path": "/analytics_cache/read", + "hb_pb": "11.10" + }, + "type": "video" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "div-1", + "price": 0.5, + "adm": " ", + "crid": "crid_10", + "mtype": 2, + "ext": { + "dspid": 6, + "origbidcpm": 11.13998874085194, + "origbidcur": "USD", + "prebid": { + "bidid": "09abd496-5706-4311-a356-d559629a1d17", + "events": { + "win": "https://fakeHost.relevant-digital.com/event?t=win\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078", + "imp": "https://fakeHost.relevant-digital.com/event?t=imp\u0026b=09abd496-5706-4311-a356-d559629a1d17\u0026a=1001\u0026bidder=providerA\u0026ts=1694939785078" + }, + "meta": { + "adaptercode": "relevantdigital" + }, + "targeting": { + "hb_bidder": "relevantdigital", + "hb_cache_host": "somedomain.com", + "hb_cache_path": "/analytics_cache/read", + "hb_pb": "11.10" + }, + "type": "video" + } + } + }, + "type": "video" + } + ], + "cur": "USD" + } + ] +} \ No newline at end of file diff --git a/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMType.json b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMType.json new file mode 100644 index 00000000000..17473625e89 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMType.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "seatbid": [ + { + "seat": "relevantdigital", + "bid": [ + { + "impid": "div-1", + "mtype": 10 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "failed to parse bid type, missing ext: div-1", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + ] + } + ] +} diff --git a/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMTypeParsesExt.json b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMTypeParsesExt.json new file mode 100644 index 00000000000..e7492a4dd3c --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMTypeParsesExt.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + }, + { + "id": "div-2", + "video": { + "mimes": null + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb818" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + }, + { + "id": "div-2", + "video": { + "mimes": null + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb818" + } + } + } + } + ], + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "seatbid": [ + { + "seat": "relevantdigital", + "bid": [ + { + "impid": "div-1", + "mtype": 0, + "ext" : { + "prebid" : { + "type": "banner" + } + } + }, + { + "impid": "div-2", + "mtype": 0, + "ext": { + "prebid": { + "type": "video" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "", + "impid": "div-1", + "price": 0, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }, + { + "bid": { + "id": "", + "impid": "div-2", + "price": 0, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + }] +} diff --git a/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidType.json b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidType.json new file mode 100644 index 00000000000..86c405c87c6 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidType.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://fakeHost.relevant-digital.com/openrtb2/auction", + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 320, + "h": 320 + } + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + } + ], + "ext": { + "prebid": { + "debug": false, + "storedrequest": { + "id": "620523ae7f4bbe1691bbb815" + } + }, + "relevant": { + "adapterType": "server", + "count": 1 + } + }, + "tmax": 750 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "seatbid": [ + { + "seat": "relevantdigital", + "bid": [ + { + "impid": "div-1", + "ext": { + "prebid" : { + "type" : "invalid" + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "invalid BidType: invalid", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] +} diff --git a/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidParam.json b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidParam.json new file mode 100644 index 00000000000..011e332548c --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidParam.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + }, + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "imp.ext not provided", + "comparison": "literal" + }, + { + "value": "ext.bidder not provided", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidRequestCount.json b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidRequestCount.json new file mode 100644 index 00000000000..37c6748d311 --- /dev/null +++ b/adapters/relevantdigital/relevantdigitaltest/supplemental/invalidRequestCount.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "3621f78b-abdf-4562-8eca-1c5e893387d0", + "imp": [ + { + "id": "div-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "accountId": "620523ae7f4bbe1691bbb815", + "pbsHost": "fakeHost", + "placementId": "620525862d7518bfd4bbb81e_620523b5d1dbed6b0fbbb817" + } + } + } + ], + "ext": { + "relevant": { + "adapterType": "server", + "count": 10 + } + }, + "tmax": 750 + }, + "expectedMakeRequestsErrors": [ + { + "value": "failed to create bidRequest, error: too many requests", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/response.go b/adapters/response.go index 080f46273cc..747b6ddf9c0 100644 --- a/adapters/response.go +++ b/adapters/response.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) func CheckResponseStatusCodeForErrors(response *ResponseData) error { @@ -15,7 +15,9 @@ func CheckResponseStatusCodeForErrors(response *ResponseData) error { } if response.StatusCode != http.StatusOK { - return fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode) + return &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + } } return nil diff --git a/adapters/response_test.go b/adapters/response_test.go index 824a295e73d..11ef1abcc5a 100644 --- a/adapters/response_test.go +++ b/adapters/response_test.go @@ -3,22 +3,38 @@ package adapters import ( "testing" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) func TestCheckResponseStatusCodeForErrors(t *testing.T) { - t.Run("bad_input", func(t *testing.T) { - err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: 400}) - expectedErr := &errortypes.BadInput{Message: "Unexpected status code: 400. Run with request.debug = 1 for more info"} - assert.Equal(t, expectedErr.Error(), err.Error()) - }) + testCases := []struct { + name string + responseStatus int + expectedErr error + }{ + { + name: "bad_input", + responseStatus: 400, + expectedErr: &errortypes.BadInput{ + Message: "Unexpected status code: 400. Run with request.debug = 1 for more info", + }, + }, + { + name: "internal_server_error", + responseStatus: 500, + expectedErr: &errortypes.BadServerResponse{ + Message: "Unexpected status code: 500. Run with request.debug = 1 for more info", + }, + }, + } - t.Run("internal_server_error", func(t *testing.T) { - err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: 500}) - expectedErrMessage := "Unexpected status code: 500. Run with request.debug = 1 for more info" - assert.Equal(t, expectedErrMessage, err.Error()) - }) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: tc.responseStatus}) + assert.Equal(t, tc.expectedErr, err) + }) + } } func TestIsResponseStatusCodeNoContent(t *testing.T) { diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go index b825708a72b..b53f8962d4d 100644 --- a/adapters/revcontent/revcontent.go +++ b/adapters/revcontent/revcontent.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/revcontent/revcontent_test.go b/adapters/revcontent/revcontent_test.go index c4816a09a0c..a39a78a81dd 100644 --- a/adapters/revcontent/revcontent_test.go +++ b/adapters/revcontent/revcontent_test.go @@ -3,9 +3,9 @@ package revcontent import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go deleted file mode 100644 index 0ac07a77252..00000000000 --- a/adapters/rhythmone/rhythmone.go +++ /dev/null @@ -1,150 +0,0 @@ -package rhythmone - -import ( - "encoding/json" - "fmt" - - "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type RhythmoneAdapter struct { - endPoint string -} - -func (a *RhythmoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) - - var uri string - request, uri, errs = a.preProcess(request, errs) - if request != nil { - reqJSON, err := json.Marshal(request) - if err != nil { - errs = append(errs, err) - return nil, errs - } - if uri != "" { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ - Method: "POST", - Uri: uri, - Body: reqJSON, - Headers: headers, - }}, errs - } - } - return nil, errs -} - -func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if response.StatusCode == http.StatusNoContent { - return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - - if response.StatusCode != http.StatusOK { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - }} - } - var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("bad server response: %d. ", err), - }} - } - - var errs []error - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), - }) - } - } - return bidResponse, errs -} - -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impId { - if imp.Banner != nil { - mediaType = openrtb_ext.BidTypeBanner - } else if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } - return mediaType - } - } - return mediaType -} - -// Builder builds a new instance of the Rythomone adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &RhythmoneAdapter{ - endPoint: config.Endpoint, - } - return bidder, nil -} - -func (a *RhythmoneAdapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb2.BidRequest, string, []error) { - numRequests := len(req.Imp) - var uri string = "" - for i := 0; i < numRequests; i++ { - imp := req.Imp[i] - var bidderExt adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &bidderExt) - if err != nil { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("ext data not provided in imp id=%s. Abort all Request", imp.ID), - } - errors = append(errors, err) - return nil, "", errors - } - var rhythmoneExt openrtb_ext.ExtImpRhythmone - err = json.Unmarshal(bidderExt.Bidder, &rhythmoneExt) - if err != nil { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("placementId | zone | path not provided in imp id=%s. Abort all Request", imp.ID), - } - errors = append(errors, err) - return nil, "", errors - } - rhythmoneExt.S2S = true - rhythmoneExtCopy, err := json.Marshal(&rhythmoneExt) - if err != nil { - errors = append(errors, err) - return nil, "", errors - } - bidderExtCopy := struct { - Bidder json.RawMessage `json:"bidder,omitempty"` - }{rhythmoneExtCopy} - impExtCopy, err := json.Marshal(&bidderExtCopy) - if err != nil { - errors = append(errors, err) - return nil, "", errors - } - imp.Ext = impExtCopy - req.Imp[i] = imp - if uri == "" { - uri = fmt.Sprintf("%s/%s/0/%s?z=%s&s2s=%s", a.endPoint, rhythmoneExt.PlacementId, rhythmoneExt.Path, rhythmoneExt.Zone, "true") - } - } - return req, uri, errors -} diff --git a/adapters/rhythmone/rhythmone_test.go b/adapters/rhythmone/rhythmone_test.go deleted file mode 100644 index 0492241dce6..00000000000 --- a/adapters/rhythmone/rhythmone_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package rhythmone - -import ( - "testing" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderRhythmone, config.Adapter{ - Endpoint: "http://tag.1rx.io/rmp"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "rhythmonetest", bidder) -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json deleted file mode 100644 index 11e89c37007..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-app.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "app": { - "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", - "name": "Yahoo Weather", - "bundle": "12345", - "storeurl": "https://itunes.apple.com/id628677149", - "cat": ["IAB15", "IAB15-10"], - "ver": "1.0.2", - "publisher": { - "id": "1" - } - }, - "device": { - "dnt": 0, - "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", - "ip": "123.145.167.189", - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", - "carrier": "VERIZON", - "language": "en", - "make": "Apple", - "model": "iPhone", - "os": "iOS", - "osv": "6.1", - "js": 1, - "connectiontype": 3, - "devicetype": 1 - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "app": { - "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", - "name": "Yahoo Weather", - "bundle": "12345", - "storeurl": "https://itunes.apple.com/id628677149", - "cat": ["IAB15", "IAB15-10"], - "ver": "1.0.2", - "publisher": { - "id": "1" - } - }, - "device": { - "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", - "ip": "123.145.167.189", - "devicetype": 1, - "make": "Apple", - "model": "iPhone", - "os": "iOS", - "osv": "6.1", - "js": 1, - "dnt": 0, - "language": "en", - "carrier": "VERIZON", - "connectiontype": 3, - "ifa": "AA000DFE74168477C70D291f574D344790E0BB11" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json deleted file mode 100644 index d6546179c24..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-gdpr.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "user": { - "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", - "ext": { - "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "user": { - "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", - "ext": { - "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [ - { - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json deleted file mode 100644 index 223ebee5fb0..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video-site.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ], - "site": { - "id": "102855", - "cat": ["IAB3-1"], - "domain": "www.foobar.com", - "page": "http://www.foobar.com/1234.html ", - "publisher": { - "id": "8953", - "name": "foobar.com", - "cat": ["IAB3-1"], - "domain": "foobar.com" - } - }, - "device": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", - "ip": "123.145.167.10" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": ["video/mp4"], - "minduration": 1, - "maxduration": 2, - "protocols": [1, 2, 5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1, 2, 3, 4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ], - "site": { - "id": "102855", - "cat": ["IAB3-1"], - "domain": "www.foobar.com", - "page": "http://www.foobar.com/1234.html ", - "publisher": { - "id": "8953", - "name": "foobar.com", - "cat": ["IAB3-1"], - "domain": "foobar.com" - } - }, - "device": { - "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", - "ip": "123.145.167.10" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": ["yahoo.com"], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} diff --git a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json b/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json deleted file mode 100644 index ae400a2d53c..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/banner-and-video.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [ - 1, - 2, - 5 - ], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [ - 2 - ], - "delivery": [ - 1 - ], - "api": [ - 1, - 2, - 3, - 4 - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - }, - { - "id": "test-imp-video-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [ - 1, - 2, - 5 - ], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [ - 2 - ], - "delivery": [ - 1 - ], - "api": [ - 1, - 2, - 3, - 4 - ] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.500000, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "h": 576, - "w": 1024 - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "7706636740145184841", - "impid": "test-imp-video-id", - "price": 0.5, - "adm": "some-test-ad", - "adid": "29681110", - "adomain": [ - "yahoo.com" - ], - "cid": "958", - "crid": "29681110", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} \ No newline at end of file diff --git a/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json b/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json deleted file mode 100644 index 6b86a9e39ee..00000000000 --- a/adapters/rhythmone/rhythmonetest/exemplary/simple-video.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [1,3,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "path": "mvo", - "zone": "1r" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "protocols": [1,3,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - }, - "ext": { - "bidder": { - "placementId": "72721", - "zone": "1r", - "path": "mvo", - "S2S": true - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "cur": "USD", - "seatbid": [ - { - "seat": "Rhythmone", - "bid": [ - { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - } - ] - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "bids": [{ - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.5, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - }, - "type": "video" - }] - } - ] -} \ No newline at end of file diff --git a/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json b/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json deleted file mode 100644 index d313a1759d4..00000000000 --- a/adapters/rhythmone/rhythmonetest/supplemental/missing-extension.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-missing-ext-id", - "video": { - "mimes": [ - "video/mp4" - ], - "minduration": 1, - "maxduration": 2, - "maxextended": 30, - "minbitrate": 300, - "maxbitrate": 1500, - "protocols": [1,2,5], - "w": 1020, - "h": 780, - "startdelay": 1, - "placement": 1, - "playbackmethod": [2], - "delivery": [1], - "api": [1,2,3,4] - } - } - ] - }, - "expectedMakeRequestsErrors": [ - { - "value": "ext data not provided in imp id=test-missing-ext-id. Abort all Request", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/richaudience/params_test.go b/adapters/richaudience/params_test.go index 038936f3cbf..4f7ede9bd55 100644 --- a/adapters/richaudience/params_test.go +++ b/adapters/richaudience/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/richaudience/richaudience.go b/adapters/richaudience/richaudience.go index 16fbe229acf..2d976cc7c7b 100644 --- a/adapters/richaudience/richaudience.go +++ b/adapters/richaudience/richaudience.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/richaudience/richaudience_test.go b/adapters/richaudience/richaudience_test.go index 30d04775c44..ee1ece29a91 100644 --- a/adapters/richaudience/richaudience_test.go +++ b/adapters/richaudience/richaudience_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/rise/rise.go b/adapters/rise/rise.go index 5570e27d6ef..56fdc4a8fff 100644 --- a/adapters/rise/rise.go +++ b/adapters/rise/rise.go @@ -8,9 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // adapter is a Rise implementation of the adapters.Bidder interface. @@ -26,9 +27,9 @@ func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) ( // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { - publisherID, err := extractPublisherID(openRTBRequest) + org, err := extractOrg(openRTBRequest) if err != nil { - errs = append(errs, fmt.Errorf("extractPublisherID: %w", err)) + errs = append(errs, fmt.Errorf("extractOrg: %w", err)) return nil, errs } @@ -43,7 +44,7 @@ func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, _ *adapters. return append(requestsToBidder, &adapters.RequestData{ Method: http.MethodPost, - Uri: a.endpointURL + "?publisher_id=" + publisherID, + Uri: a.endpointURL + "?publisher_id=" + org, Body: openRTBRequestJSON, Headers: headers, }), nil @@ -87,7 +88,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData return bidResponse, errs } -func extractPublisherID(openRTBRequest *openrtb2.BidRequest) (string, error) { +func extractOrg(openRTBRequest *openrtb2.BidRequest) (string, error) { var err error for _, imp := range openRTBRequest.Imp { var bidderExt adapters.ExtImpBidder @@ -100,12 +101,15 @@ func extractPublisherID(openRTBRequest *openrtb2.BidRequest) (string, error) { return "", fmt.Errorf("unmarshal ImpExtRise: %w", err) } + if impExt.Org != "" { + return strings.TrimSpace(impExt.Org), nil + } if impExt.PublisherID != "" { return strings.TrimSpace(impExt.PublisherID), nil } } - return "", errors.New("no publisherID supplied") + return "", errors.New("no org or publisher_id supplied") } func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { diff --git a/adapters/rise/rise_test.go b/adapters/rise/rise_test.go index 1ba3f8a865d..79d34bde64f 100644 --- a/adapters/rise/rise_test.go +++ b/adapters/rise/rise_test.go @@ -3,9 +3,9 @@ package rise import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "risetest" diff --git a/adapters/rise/risetest/exemplary/banner-and-video-app.json b/adapters/rise/risetest/exemplary/banner-and-video-app.json index 9eb79156403..fa267977f72 100644 --- a/adapters/rise/risetest/exemplary/banner-and-video-app.json +++ b/adapters/rise/risetest/exemplary/banner-and-video-app.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -41,7 +41,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -98,7 +98,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } @@ -121,7 +121,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } diff --git a/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json b/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json index dfdf54a8c67..980b62446b5 100644 --- a/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json +++ b/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -41,7 +41,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -83,7 +83,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } @@ -106,7 +106,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } diff --git a/adapters/rise/risetest/exemplary/banner-and-video.json b/adapters/rise/risetest/exemplary/banner-and-video.json index 2a2e3f681a1..d0d6a9bcc44 100644 --- a/adapters/rise/risetest/exemplary/banner-and-video.json +++ b/adapters/rise/risetest/exemplary/banner-and-video.json @@ -55,7 +55,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -123,7 +123,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } diff --git a/adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json similarity index 85% rename from adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json rename to adapters/rise/risetest/exemplary/simple-banner-both-ids.json index d2acf3612af..99f9e1f211f 100644 --- a/adapters/rhythmone/rhythmonetest/exemplary/simple-banner.json +++ b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json @@ -18,7 +18,8 @@ }, "ext": { "bidder": { - "placementId": "72721", + "org": "72720", + "publisher_id": "72721", "path": "mvo", "zone": "1r" } @@ -29,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://tag.1rx.io/rmp/72721/0/mvo?z=1r&s2s=true", + "uri": "http://localhost/prebid_server?publisher_id=72720", "body": { "id": "test-request-id", "imp": [ @@ -49,10 +50,10 @@ }, "ext": { "bidder": { - "placementId": "72721", + "org": "72720", + "publisher_id": "72721", "zone": "1r", - "path": "mvo", - "S2S": true + "path": "mvo" } } } @@ -65,7 +66,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "7706636740145184841", @@ -79,7 +80,8 @@ "cid": "958", "crid": "29681110", "h": 250, - "w": 300 + "w": 300, + "mtype": 1 } ] } @@ -105,10 +107,11 @@ "cid": "958", "crid": "29681110", "w": 300, - "h": 250 + "h": 250, + "mtype": 1 }, "type": "banner" }] } ] -} \ No newline at end of file +} diff --git a/adapters/rise/risetest/exemplary/simple-banner.json b/adapters/rise/risetest/exemplary/simple-banner.json index 13e965d1e2f..1fba0f398cb 100644 --- a/adapters/rise/risetest/exemplary/simple-banner.json +++ b/adapters/rise/risetest/exemplary/simple-banner.json @@ -64,7 +64,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "7706636740145184841", diff --git a/adapters/rise/risetest/exemplary/simple-video.json b/adapters/rise/risetest/exemplary/simple-video.json index 16ca8ac72dc..0854c06c59f 100644 --- a/adapters/rise/risetest/exemplary/simple-video.json +++ b/adapters/rise/risetest/exemplary/simple-video.json @@ -21,7 +21,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -55,7 +55,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } @@ -71,7 +71,7 @@ "cur": "USD", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", diff --git a/adapters/rise/risetest/supplemental/bad-request.json b/adapters/rise/risetest/supplemental/bad-request.json index f2ba84b1c78..b5bedc090d9 100644 --- a/adapters/rise/risetest/supplemental/bad-request.json +++ b/adapters/rise/risetest/supplemental/bad-request.json @@ -21,7 +21,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -55,7 +55,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } diff --git a/adapters/rise/risetest/supplemental/missing-bidder.json b/adapters/rise/risetest/supplemental/missing-bidder.json index 2bda5161c67..0a7cc67ca08 100644 --- a/adapters/rise/risetest/supplemental/missing-bidder.json +++ b/adapters/rise/risetest/supplemental/missing-bidder.json @@ -28,7 +28,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "extractPublisherID: unmarshal ImpExtRise: unexpected end of JSON input", + "value": "extractOrg: unmarshal ImpExtRise: unexpected end of JSON input", "comparison": "literal" } ] diff --git a/adapters/rise/risetest/supplemental/missing-extension.json b/adapters/rise/risetest/supplemental/missing-extension.json index db8dbdf74d9..74e94f45c79 100644 --- a/adapters/rise/risetest/supplemental/missing-extension.json +++ b/adapters/rise/risetest/supplemental/missing-extension.json @@ -27,7 +27,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "extractPublisherID: unmarshal bidderExt: unexpected end of JSON input", + "value": "extractOrg: unmarshal bidderExt: unexpected end of JSON input", "comparison": "literal" } ] diff --git a/adapters/rise/risetest/supplemental/missing-mtype.json b/adapters/rise/risetest/supplemental/missing-mtype.json index be45db584ea..79fe4b38406 100644 --- a/adapters/rise/risetest/supplemental/missing-mtype.json +++ b/adapters/rise/risetest/supplemental/missing-mtype.json @@ -21,7 +21,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "path": "mvo", "zone": "1r" } @@ -55,7 +55,7 @@ }, "ext": { "bidder": { - "publisher_id": "72721", + "org": "72721", "zone": "1r", "path": "mvo" } @@ -71,7 +71,7 @@ "cur": "USD", "seatbid": [ { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", @@ -85,7 +85,7 @@ ] }, { - "seat": "Rhythmone", + "seat": "958", "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", diff --git a/adapters/rise/risetest/supplemental/missing-publisher-id.json b/adapters/rise/risetest/supplemental/missing-org.json similarity index 89% rename from adapters/rise/risetest/supplemental/missing-publisher-id.json rename to adapters/rise/risetest/supplemental/missing-org.json index 3a14b213160..4107d40e1c3 100644 --- a/adapters/rise/risetest/supplemental/missing-publisher-id.json +++ b/adapters/rise/risetest/supplemental/missing-org.json @@ -24,6 +24,7 @@ }, "ext": { "bidder": { + "org": "", "publisher_id": "" }} } @@ -31,7 +32,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "extractPublisherID: no publisherID supplied", + "value": "extractOrg: no org or publisher_id supplied", "comparison": "literal" } ] diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index c8afb0c4259..92e0e57b287 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -2,16 +2,28 @@ package rtbhouse import ( "encoding/json" + "errors" "fmt" "net/http" + "strings" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) +const ( + BidderCurrency string = "USD" +) + +// RTBHouseAdapter implements the Bidder interface. +type RTBHouseAdapter struct { + endpoint string +} + // Builder builds a new instance of the RTBHouse adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &RTBHouseAdapter{ @@ -20,11 +32,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -// RTBHouseAdapter implements the Bidder interface. -type RTBHouseAdapter struct { - endpoint string -} - // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (adapter *RTBHouseAdapter) MakeRequests( openRTBRequest *openrtb2.BidRequest, @@ -33,7 +40,51 @@ func (adapter *RTBHouseAdapter) MakeRequests( requestsToBidder []*adapters.RequestData, errs []error, ) { - openRTBRequestJSON, err := json.Marshal(openRTBRequest) + + reqCopy := *openRTBRequest + reqCopy.Imp = []openrtb2.Imp{} + for _, imp := range openRTBRequest.Imp { + var bidFloorCur = imp.BidFloorCur + var bidFloor = imp.BidFloor + if bidFloorCur == "" && bidFloor == 0 { + rtbhouseExt, err := getImpressionExt(imp) + if err != nil { + return nil, []error{err} + } + if rtbhouseExt.BidFloor > 0 { + bidFloor = rtbhouseExt.BidFloor + bidFloorCur = BidderCurrency + if len(reqCopy.Cur) > 0 { + bidFloorCur = reqCopy.Cur[0] + } + } + } + + // Check if imp comes with bid floor amount defined in a foreign currency + if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != BidderCurrency { + // Convert to US dollars + convertedValue, err := reqInfo.ConvertCurrency(bidFloor, bidFloorCur, BidderCurrency) + if err != nil { + return nil, []error{err} + } + + bidFloorCur = BidderCurrency + bidFloor = convertedValue + } + + if bidFloor > 0 && bidFloorCur == BidderCurrency { + // Update after conversion. All imp elements inside request.Imp are shallow copies + // therefore, their non-pointer values are not shared memory and are safe to modify. + imp.BidFloorCur = bidFloorCur + imp.BidFloor = bidFloor + } + + // Set the CUR of bid to BIDDER_CURRENCY after converting all floors + reqCopy.Cur = []string{BidderCurrency} + reqCopy.Imp = append(reqCopy.Imp, imp) + } + + openRTBRequestJSON, err := json.Marshal(reqCopy) if err != nil { errs = append(errs, err) return nil, errs @@ -52,6 +103,24 @@ func (adapter *RTBHouseAdapter) MakeRequests( return requestsToBidder, errs } +func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpRTBHouse, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Bidder extension not provided or can't be unmarshalled", + } + } + + var rtbhouseExt openrtb_ext.ExtImpRTBHouse + if err := json.Unmarshal(bidderExt.Bidder, &rtbhouseExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + + return &rtbhouseExt, nil +} + const unexpectedStatusCodeFormat = "" + "Unexpected status code: %d. Run with request.debug = 1 for more info" @@ -92,11 +161,63 @@ func (adapter *RTBHouseAdapter) MakeBids( for _, seatBid := range openRTBBidderResponse.SeatBid { for _, bid := range seatBid.Bid { bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this - typedBid = &adapters.TypedBid{Bid: &bid, BidType: "banner"} - bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } else { + typedBid = &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + + // for native bid responses fix Adm field + if typedBid.BidType == openrtb_ext.BidTypeNative { + bid.AdM, err = getNativeAdm(bid.AdM) + if err != nil { + errs = append(errs, err) + continue + } + } + + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } } } - return bidderResponse, nil + bidderResponse.Currency = BidderCurrency + + return bidderResponse, errs + +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unrecognized bid type in response from rtbhouse for bid %s", bid.ImpID) + } +} + +func getNativeAdm(adm string) (string, error) { + nativeAdm := make(map[string]interface{}) + err := json.Unmarshal([]byte(adm), &nativeAdm) + if err != nil { + return adm, errors.New("unable to unmarshal native adm") + } + + // move bid.adm.native to bid.adm + if _, ok := nativeAdm["native"]; ok { + //using jsonparser to avoid marshaling, encode escape, etc. + value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) + if err != nil || dataType != jsonparser.Object { + return adm, errors.New("unable to get native adm") + } + adm = string(value) + } + return adm, nil } diff --git a/adapters/rtbhouse/rtbhouse_test.go b/adapters/rtbhouse/rtbhouse_test.go index e367b921957..7bccfa36266 100644 --- a/adapters/rtbhouse/rtbhouse_test.go +++ b/adapters/rtbhouse/rtbhouse_test.go @@ -3,9 +3,9 @@ package rtbhouse import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testsDir = "rtbhousetest" diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json new file mode 100644 index 00000000000..79aa038c3ca --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 3.00 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 3.00, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 3.00 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json new file mode 100644 index 00000000000..99b3a87ebfa --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "cur": [ + "EUR" + ], + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json new file mode 100644 index 00000000000..85e8a1c28cf --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "publisherId": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345" + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json new file mode 100644 index 00000000000..1417965741e --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "ext": { + "bidder": { + "publisherId": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 1, + "ext": { + "bidder": { + "publisherId": "12345" + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json new file mode 100644 index 00000000000..e79b21a1207 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json new file mode 100644 index 00000000000..9f5962fd3a1 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json index 3a636450f3c..468e73f2aca 100644 --- a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json +++ b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json @@ -23,6 +23,7 @@ "uri": "http://localhost/prebid_server", "body": { "id": "test-request-id", + "cur": ["USD"], "site": { "page": "https://good.site/url" }, @@ -55,7 +56,8 @@ "cid": "987", "crid": "12345678", "h": 250, - "w": 300 + "w": 300, + "mtype": 1 }] }], "cur": "USD" @@ -75,7 +77,8 @@ "cid": "987", "crid": "12345678", "w": 300, - "h": 250 + "h": 250, + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json b/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json new file mode 100644 index 00000000000..fc706d97004 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2.00 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "bidfloor": 0.05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "publisherId": "12345", + "bidfloor": 2 + } + }, + "id": "test-imp-id" + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 300, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json index f84f5555259..e0861fb027d 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json @@ -26,6 +26,7 @@ "uri": "http://localhost/prebid_server", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [ { "id": "test-imp-id", diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json b/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json new file mode 100644 index 00000000000..6e6f415acbc --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + }, + { + "id": "test-banner-imp", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-multi-slot-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-multi-slot-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + }, + { + "id": "test-banner-imp", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-multi-slot-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid-native", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"title\":{\"text\":\"title text\"}},{\"id\":1,\"data\":{\"value\":\"data value\"}},{\"id\":2,\"img\":{\"url\":\"image.url\",\"w\":1200,\"h\":628}}],\"link\":{\"url\":\"link.url\"},\"imptrackers\":[\"imp.tracker.url\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"event.tracker.url\"}]}}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 99 + }, + { + "id": "randomid-banner", + "impid": "test-banner-imp", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid-banner", + "impid": "test-banner-imp", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-native-imp", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json b/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json new file mode 100644 index 00000000000..d7d65e43e76 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "JPY", + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Currency conversion rate not found: 'JPY' => 'USD'", + "comparison": "literal" + } + ] + } diff --git a/adapters/brightroll/brightrolltest/supplemental/missing-param.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json similarity index 66% rename from adapters/brightroll/brightrolltest/supplemental/missing-param.json rename to adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json index 08e82c0e31c..70fddf620e6 100644 --- a/adapters/brightroll/brightrolltest/supplemental/missing-param.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json @@ -3,31 +3,27 @@ "id": "test-request-id", "imp": [ { - "id": "test-missing-req-param-id", + "id": "banner-imp-id", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, "ext": { "bidder": { - "publisher":"" + "publisherId": 12345 } } - } - ] + ], + "ext": {} }, "expectedMakeRequestsErrors": [ { - "value": "publisher is empty", + "value": "Error while unmarshaling bidder extension", "comparison": "literal" } ] diff --git a/adapters/brightroll/brightrolltest/supplemental/invalid-extension.json b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json similarity index 60% rename from adapters/brightroll/brightrolltest/supplemental/invalid-extension.json rename to adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json index 35b1563822f..f416f8106fa 100644 --- a/adapters/brightroll/brightrolltest/supplemental/invalid-extension.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json @@ -3,28 +3,22 @@ "id": "test-request-id", "imp": [ { - "id": "test-invalid-ext-id", + "id": "banner-imp-id", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] - }, - "ext": { } - } - ] + ], + "ext": {} }, "expectedMakeRequestsErrors": [ { - "value": "ext.bidder.publisher not provided", + "value": "Bidder extension not provided or can't be unmarshalled", "comparison": "literal" } ] diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json new file mode 100644 index 00000000000..5e1aa0ef4b7 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"native\":\"faulty object\"}", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unable to get native adm", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json new file mode 100644 index 00000000000..41cc909f304 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "imp": [ + { + "ext": { + "bidder": {} + }, + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "id": "test-native-request", + "ext": {}, + "debug": 1 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-native-request", + "cur": [ + "USD" + ], + "imp": [ + { + "id": "test-native-imp", + "native": { + "request": "{\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":1,\"required\":1,\"data\":{\"type\":2}},{\"id\":2,\"required\":1,\"img\":{\"type\":3}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "page": "https://good.site/url" + }, + "ext": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-native-request", + "bidid": "test-bidid", + "cur": "USD", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "test-native-request", + "impid": "test-native-imp", + "price": 0.5, + "adid": "test-adid", + "adm": "{\"ver\":\"1.2\"", + "adomain": [ "adomain.com" ], + "cid": "test-cid", + "crid": "test-crid", + "dealid": "test-dealid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unable to unmarshal native adm", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json new file mode 100644 index 00000000000..fc52cc30601 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 99 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json new file mode 100644 index 00000000000..8bb9e8a1f89 --- /dev/null +++ b/adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-id", + "cur": [ + "USD" + ], + "site": { + "page": "https://good.site/url" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "rtbhouse", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized bid type in response from rtbhouse for bid test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json index 0702c103332..bb7754ec1fc 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json @@ -26,6 +26,7 @@ "uri": "http://localhost/prebid_server", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [ { "id": "test-imp-id", diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json index 65d21406bf0..0b5279326d2 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json @@ -26,6 +26,7 @@ "uri": "http://localhost/prebid_server", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [ { "id": "test-imp-id", diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json index 4c5dd576aa6..6ff3860ee65 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json @@ -26,6 +26,7 @@ "uri": "http://localhost/prebid_server", "body": { "id": "test-request-id", + "cur": ["USD"], "imp": [ { "id": "test-imp-id", diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 99072255586..7d09224e8b4 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -8,11 +8,11 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/maputil" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/adcom1" @@ -98,11 +98,10 @@ type rubiconDataExt struct { } type rubiconUserExt struct { - Eids []openrtb2.EID `json:"eids,omitempty"` - RP rubiconUserExtRP `json:"rp"` - LiverampIdl string `json:"liveramp_idl,omitempty"` - Data json.RawMessage `json:"data,omitempty"` - Consent string `json:"consent,omitempty"` + Eids []openrtb2.EID `json:"eids,omitempty"` + RP rubiconUserExtRP `json:"rp"` + Data json.RawMessage `json:"data,omitempty"` + Consent string `json:"consent,omitempty"` } type rubiconSiteExtRP struct { @@ -176,12 +175,6 @@ type rubiconUserExtEidExt struct { Segments []string `json:"segments,omitempty"` } -// defines the contract for bidrequest.user.ext.eids[i].uids[j].ext -type rubiconUserExtEidUidExt struct { - RtiPartner string `json:"rtiPartner,omitempty"` - Stype string `json:"stype"` -} - type mappedRubiconUidsParam struct { segments []string liverampIdl string @@ -346,19 +339,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if userBuyerUID == "" { userBuyerUID = extractUserBuyerUID(userExtRP.Eids) } - - mappedRubiconUidsParam, errors := getSegments(userExtRP.Eids) - if len(errors) > 0 { - errs = append(errs, errors...) - continue - } - - if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil { - errs = append(errs, err) - continue - } - - userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl } if userCopy.Consent != "" { @@ -940,81 +920,13 @@ func extractUserBuyerUID(eids []openrtb2.EID) string { } for _, uid := range eid.UIDs { - var uidExt rubiconUserExtEidUidExt - err := json.Unmarshal(uid.Ext, &uidExt) - if err != nil { - continue - } - - if uidExt.Stype == "ppuid" || uidExt.Stype == "other" { - return uid.ID - } + return uid.ID } } return "" } -func getSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error) { - rubiconUidsParam := mappedRubiconUidsParam{ - segments: make([]string, 0), - } - errs := make([]error, 0) - - for _, eid := range eids { - switch eid.Source { - case "liveintent.com": - uids := eid.UIDs - if len(uids) > 0 { - if eid.Ext != nil { - var eidExt rubiconUserExtEidExt - if err := json.Unmarshal(eid.Ext, &eidExt); err != nil { - errs = append(errs, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - rubiconUidsParam.segments = eidExt.Segments - } - } - case "liveramp.com": - uids := eid.UIDs - if len(uids) > 0 { - uidId := uids[0].ID - if uidId != "" && rubiconUidsParam.liverampIdl == "" { - rubiconUidsParam.liverampIdl = uidId - } - } - } - } - - return rubiconUidsParam, errs -} - -func updateUserExtWithSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error { - if len(rubiconUidsParam.segments) > 0 { - - if rubiconUidsParam.segments != nil { - userExtRPTarget := make(map[string]interface{}) - - if userExtRP.RP.Target != nil { - if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } - } - - userExtRPTarget["LIseg"] = rubiconUidsParam.segments - - if target, err := json.Marshal(&userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } else { - userExtRP.RP.Target = target - } - } - } - return nil -} - func isVideo(imp openrtb2.Imp) bool { video := imp.Video if video != nil { @@ -1047,8 +959,11 @@ func resolveNativeObject(native *openrtb2.Native, target map[string]interface{}) return nil, fmt.Errorf("Eventtrackers are not present or not of array type") } - if _, ok := target["context"].(float64); !ok { - return nil, fmt.Errorf("Context is not present or not of int type") + context := target["context"] + if context != nil { + if _, ok := context.(float64); !ok { + return nil, fmt.Errorf("Context is not of int type") + } } if _, ok := target["plcmttype"].(float64); !ok { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index b665746e1a8..334a5e269c0 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -8,11 +8,11 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/adcom1" @@ -123,7 +123,12 @@ func TestResolveNativeObject(t *testing.T) { { nativeObject: openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": [], \"context\": \"someWrongValue\"}"}, target: map[string]interface{}{}, - expectedError: fmt.Errorf("Context is not present or not of int type"), + expectedError: fmt.Errorf("Context is not of int type"), + }, + { + nativeObject: openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": [], \"plcmttype\": 2}"}, + target: map[string]interface{}{}, + expectedError: nil, }, { nativeObject: openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": [], \"context\": 1}"}, @@ -290,12 +295,12 @@ type mockCurrencyConversion struct { mock.Mock } -func (m mockCurrencyConversion) GetRate(from string, to string) (float64, error) { +func (m *mockCurrencyConversion) GetRate(from string, to string) (float64, error) { args := m.Called(from, to) return args.Get(0).(float64), args.Error(1) } -func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { +func (m *mockCurrencyConversion) GetRates() *map[string]map[string]float64 { args := m.Called() return args.Get(0).(*map[string]map[string]float64) } @@ -639,97 +644,6 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { assert.Equal(t, badvOverflowed[:50], badvRequest, "Unexpected dfp_ad_unit_code: %s") } -func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { - bidder := new(RubiconAdapter) - - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{{ - ID: "test-imp-id", - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{ - {W: 300, H: 250}, - }, - }, - Ext: json.RawMessage(`{"bidder": { - "zoneId": 8394, - "siteId": 283282, - "accountId": 7891 - }}`), - }}, - App: &openrtb2.App{ - ID: "com.test", - Name: "testApp", - }, - User: &openrtb2.User{ - Ext: json.RawMessage(`{"eids": [ - { - "source": "pubcid", - "uids": [{ - "id": "2402fc76-7b39-4f0e-bfc2-060ef7693648" - }] - }, - { - "source": "adserver.org", - "uids": [{ - "id": "3d50a262-bd8e-4be3-90b8-246291523907", - "ext": { - "rtiPartner": "TDID" - } - }] - }, - { - "source": "liveintent.com", - "uids": [{ - "id": "T7JiRRvsRAmh88" - }], - "ext": { - "segments": ["999","888"] - } - }, - { - "source": "liveramp.com", - "uids": [{ - "id": "LIVERAMPID" - }], - "ext": { - "segments": ["111","222"] - } - } - ]}`), - }, - } - - reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - rubiconReq := &openrtb2.BidRequest{} - if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { - t.Fatalf("Unexpected error while decoding request: %s", err) - } - - assert.NotNil(t, rubiconReq.User.Ext, "User.Ext object should not be nil.") - - var userExt rubiconUserExt - if err := json.Unmarshal(rubiconReq.User.Ext, &userExt); err != nil { - t.Fatal("Error unmarshalling request.user.ext object.") - } - - assert.NotNil(t, userExt.Eids) - assert.Equal(t, 4, len(userExt.Eids), "Eids values are not as expected!") - - // liveramp.com - assert.Equal(t, "LIVERAMPID", userExt.LiverampIdl, "Liveramp_idl value is not as expected!") - - userExtRPTarget := make(map[string]interface{}) - if err := json.Unmarshal(userExt.RP.Target, &userExtRPTarget); err != nil { - t.Fatal("Error unmarshalling request.user.ext.rp.target object.") - } - - assert.Contains(t, userExtRPTarget, "LIseg", "request.user.ext.rp.target value is not as expected!") - assert.Contains(t, userExtRPTarget["LIseg"], "888", "No segment with 888 as expected!") - assert.Contains(t, userExtRPTarget["LIseg"], "999", "No segment with 999 as expected!") -} - func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t *testing.T) { bidder := new(RubiconAdapter) diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json deleted file mode 100644 index dea75ac513e..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-other-stype-present.json +++ /dev/null @@ -1,372 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "other" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "uidId", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "other" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json deleted file mode 100644 index c2b129ebdba..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-not-present-but-special-uid-with-ppuid-stype-present.json +++ /dev/null @@ -1,372 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "uidId", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json b/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json deleted file mode 100644 index 458e19ee5a3..00000000000 --- a/adapters/rubicon/rubicontest/exemplary/user-buyeruid-present.json +++ /dev/null @@ -1,373 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch" - }, - "user": { - "buyeruid": "buyeruid", - "yob": 2000, - "geo": { - "country": "USA", - "lat": 47.627500, - "lon": -122.346200 - }, - "gender": "f", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "keywords": "someKeywords", - "ext": { - "rp": { - "target": { - "someKey": "someValue" - } - }, - "data": { - "dataKey1": "dataValue1", - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": true - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - } - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "video": { - "placement": 3, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "someAdSlot" - }, - "dataAttr1": "dataVal1", - "dataAttr2": "dataVal2" - }, - "bidder": { - "video": { - }, - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "uri?tk_xint=pbs-test-tracker", - "body": { - "id": "test-request-id", - "device": { - "ext": { - "rp": { - "pixelratio": 0 - } - }, - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "site": { - "page": "somePage", - "ref": "someRef", - "search": "someSearch", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - } - }, - "user": { - "buyeruid": "buyeruid", - "data": [ - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy" - } - ] - }, - { - "ext": { - "segtax": "someValue" - }, - "segment": [ - { - "id": "shouldNotBeCopied" - } - ] - }, - { - "ext": { - "segtax": "4" - }, - "segment": [ - { - "id": "shouldNotBeCopied2" - } - ] - }, - { - "ext": { - "segtax": 4 - }, - "segment": [ - { - "id": "idToCopy2" - } - ] - }, - { - "ext": { - "segtax": [ - 4 - ] - }, - "segment": [ - { - "id": "shouldNotBeCopied3" - } - ] - } - ], - "ext": { - "rp": { - "target": { - "someKey": "someValue", - "dataKey1": [ - "dataValue1" - ], - "dataKey2": [ - "dataValue2", - "dataValue3" - ], - "dataKey3": [ - "true" - ], - "iab": [ - "idToCopy", - "idToCopy2" - ] - } - }, - "eids": [ - { - "source": "rubiconproject.com", - "uids": [ - { - "id": "uidId", - "ext": { - "stype": "ppuid" - } - } - ] - } - ] - }, - "keywords": "someKeywords" - }, - "imp": [ - { - "id": "test-imp-id", - "instl": 1, - "secure": 1, - "video": { - "placement": 3, - "ext": { - "rp": { - "size_id": 203 - } - }, - "mimes": [ - "video/mp4" - ], - "protocols": [ - 2, - 5 - ], - "w": 1024, - "h": 576 - }, - "ext": { - "rp": { - "target": { - "dataAttr1": [ - "dataVal1" - ], - "dataAttr2": [ - "dataVal2" - ], - "page": [ - "somePage" - ], - "ref": [ - "someRef" - ], - "search": [ - "someSearch" - ], - "dfp_ad_unit_code": "someAdSlot" - }, - "track": { - "mint": "", - "mint_version": "" - }, - "zone_id": 535510 - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adman" - } - ], - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "some-test-ad", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go index 070c97741b0..568ac83d9cf 100644 --- a/adapters/sa_lunamedia/params_test.go +++ b/adapters/sa_lunamedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go index 9c9c34a6bf3..0d7d253114b 100644 --- a/adapters/sa_lunamedia/salunamedia.go +++ b/adapters/sa_lunamedia/salunamedia.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go index 9aa15187ab1..4bcfb96f28e 100644 --- a/adapters/sa_lunamedia/salunamedia_test.go +++ b/adapters/sa_lunamedia/salunamedia_test.go @@ -3,9 +3,9 @@ package salunamedia import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/screencore/params_test.go b/adapters/screencore/params_test.go new file mode 100644 index 00000000000..7220f9945cf --- /dev/null +++ b/adapters/screencore/params_test.go @@ -0,0 +1,50 @@ +package screencore + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var validParams = []string{ + `{ "accountId": "hash", "placementId": "hash"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderScreencore, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Screencore params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `5.2`, + `[]`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "placementId": "", "accountId": "" }`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderScreencore, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/screencore/screencore.go b/adapters/screencore/screencore.go new file mode 100644 index 00000000000..d5c2899af20 --- /dev/null +++ b/adapters/screencore/screencore.go @@ -0,0 +1,176 @@ +package screencore + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + return bidder, nil +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return headers +} + +func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + if len(openRTBRequest.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "Empty Imp array in BidRequest", + }} + } + + screencoreExt, err := getImpressionExt(&openRTBRequest.Imp[0]) + if err != nil { + return nil, []error{err} + } + + openRTBRequest.Imp[0].Ext = nil + + url, err := a.buildEndpointURL(screencoreExt) + if err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + Headers: getHeaders(openRTBRequest), + }}, nil +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtScreencore, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error parsing screencoreExt - " + err.Error(), + } + } + var screencoreExt openrtb_ext.ExtScreencore + if err := json.Unmarshal(bidderExt.Bidder, &screencoreExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error parsing bidderExt - " + err.Error(), + } + } + + return &screencoreExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtScreencore) (string, error) { + endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID, SourceId: params.PlacementID} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func checkResponseStatusCodes(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong Status Code: [ %d ] ", response.StatusCode), + } + } + + return adapters.CheckResponseStatusCodeForErrors(response) +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { + if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) { + return nil, nil + } + + httpStatusError := checkResponseStatusCodes(bidderRawResponse) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + var bidsArray []*adapters.TypedBid + + for _, sb := range bidResp.SeatBid { + for idx, bid := range sb.Bid { + bidType, err := getMediaTypeForImp(bid) + if err != nil { + return nil, []error{err} + } + + bidsArray = append(bidsArray, &adapters.TypedBid{ + Bid: &sb.Bid[idx], + BidType: bidType, + }) + } + } + + bidResponse.Bids = bidsArray + return bidResponse, nil +} + +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unsupported MType %d", bid.MType) + } +} diff --git a/adapters/screencore/screencore_test.go b/adapters/screencore/screencore_test.go new file mode 100644 index 00000000000..4dc22b3cd6a --- /dev/null +++ b/adapters/screencore/screencore_test.go @@ -0,0 +1,20 @@ +package screencore + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderScreencore, config.Adapter{ + Endpoint: "http://h1.screencore.io/?kp={{.AccountID}}&kn={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "screencoretest", bidder) +} diff --git a/adapters/screencore/screencoretest/exemplary/banner-app.json b/adapters/screencore/screencoretest/exemplary/banner-app.json new file mode 100644 index 00000000000..0b75398d767 --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/banner-app.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "1", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "type": "banner", + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/exemplary/banner-web.json b/adapters/screencore/screencoretest/exemplary/banner-web.json new file mode 100644 index 00000000000..a9eed6bfb53 --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/banner-web.json @@ -0,0 +1,197 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + }, + { + "id": "some-impression-id2", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + } + }, + { + "id": "some-impression-id2", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + { + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "some-impression-id2", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "type": "banner", + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id1", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e162", + "impid": "some-impression-id2", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/exemplary/native-app.json b/adapters/screencore/screencoretest/exemplary/native-app.json new file mode 100644 index 00000000000..eece64eea2d --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/native-app.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "mtype": 4 + } + ], + "type": "native", + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/exemplary/native-web.json b/adapters/screencore/screencoretest/exemplary/native-web.json new file mode 100644 index 00000000000..5878519f33f --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/native-web.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "native": { + "ver": "1.1", + "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "mtype": 4 + } + ], + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/exemplary/video-app.json b/adapters/screencore/screencoretest/exemplary/video-app.json new file mode 100644 index 00000000000..7368af33a98 --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/video-app.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "mtype": 2 + } + ], + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/exemplary/video-web.json b/adapters/screencore/screencoretest/exemplary/video-web.json new file mode 100644 index 00000000000..a56ddb78bea --- /dev/null +++ b/adapters/screencore/screencoretest/exemplary/video-web.json @@ -0,0 +1,159 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + } + ], + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/bad_media_type.json b/adapters/screencore/screencoretest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..ed7200c54a6 --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/bad_media_type.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "1", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "1", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "test-imp-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "mtype": 0, + "seat": "screencore" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unsupported MType 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/empty-imp-array.json b/adapters/screencore/screencoretest/supplemental/empty-imp-array.json new file mode 100644 index 00000000000..e19996fbe2a --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/empty-imp-array.json @@ -0,0 +1,16 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Empty Imp array in BidRequest", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] + } \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/empty-seatbid-array.json b/adapters/screencore/screencoretest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..0ed6159a54a --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/empty-seatbid-array.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "mockResponse": { + "status": 200, + "body": "invalid response" + }, + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/invalid-bidder-ext-object.json b/adapters/screencore/screencoretest/supplemental/invalid-bidder-ext-object.json new file mode 100644 index 00000000000..4dc5c5a62bc --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/invalid-bidder-ext-object.json @@ -0,0 +1,33 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Error parsing bidderExt - json: cannot unmarshal string into Go value of type openrtb_ext.ExtScreencore", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": "wrongBidderExt" + } + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/invalid-response.json b/adapters/screencore/screencoretest/supplemental/invalid-response.json new file mode 100644 index 00000000000..b2417d9d8d6 --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/invalid-response.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/invalid-screencore-ext-object.json b/adapters/screencore/screencoretest/supplemental/invalid-screencore-ext-object.json new file mode 100644 index 00000000000..b52e18fab50 --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/invalid-screencore-ext-object.json @@ -0,0 +1,31 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Error parsing screencoreExt - json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "wrongscreencoreExt" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/status-code-bad-request.json b/adapters/screencore/screencoretest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..68deeef92de --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/status-code-bad-request.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/status-code-no-content.json b/adapters/screencore/screencoretest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..1b6fa699a1b --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/status-code-no-content.json @@ -0,0 +1,76 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/status-code-other-error.json b/adapters/screencore/screencoretest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..994b33f2b80 --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/status-code-other-error.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 306. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/screencore/screencoretest/supplemental/status-code-service-unavailable.json b/adapters/screencore/screencoretest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..b885df20c58 --- /dev/null +++ b/adapters/screencore/screencoretest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountId": "accountId", + "placementId": "placementId" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://h1.screencore.io/?kp=accountId&kn=placementId", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/seedingAlliance/params_test.go b/adapters/seedingAlliance/params_test.go index cdf29aed9e7..deb964a3743 100644 --- a/adapters/seedingAlliance/params_test.go +++ b/adapters/seedingAlliance/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { @@ -36,8 +36,12 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"adUnitId": "1234"}`, `{"adUnitId": "AB12"}`, + `{"adUnitId": "1234", "seatId": "1234"}`, + `{"adUnitId": "AB12", "seatId": "AB12"}`, } var invalidParams = []string{ `{"adUnitId": 42}`, + `{"adUnitId": "1234", "seatId": 42}`, + `{"adUnitId": 1234, "seatId": "42"}`, } diff --git a/adapters/seedingAlliance/seedingAlliance.go b/adapters/seedingAlliance/seedingAlliance.go index 683cb6db8b7..92b339d1fc4 100644 --- a/adapters/seedingAlliance/seedingAlliance.go +++ b/adapters/seedingAlliance/seedingAlliance.go @@ -6,28 +6,38 @@ import ( "net/http" "strconv" "strings" + "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { - endpoint string + endpoint *template.Template } func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + bidder := &adapter{ - endpoint: config.Endpoint, + endpoint: template, } return bidder, nil } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var seatId string + var err error + for i := range request.Imp { - if err := addTagID(&request.Imp[i]); err != nil { + if seatId, err = getExtInfo(&request.Imp[i]); err != nil { return nil, []error{err} } } @@ -41,9 +51,14 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *a return nil, []error{err} } + url, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{AccountID: seatId}) + if err != nil { + return nil, []error{err} + } + requestData := &adapters.RequestData{ Method: http.MethodPost, - Uri: a.endpoint, + Uri: url, Body: requestJSON, } @@ -127,19 +142,25 @@ func curExists(allowedCurrencies []string, newCurrency string) bool { return false } -func addTagID(imp *openrtb2.Imp) error { +func getExtInfo(imp *openrtb2.Imp) (string, error) { var ext adapters.ExtImpBidder var extSA openrtb_ext.ImpExtSeedingAlliance + seatID := "pbs" + if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err) + return "", fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err) } if err := json.Unmarshal(ext.Bidder, &extSA); err != nil { - return fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSeedingAlliance: %w", err) + return "", fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSeedingAlliance: %w", err) } imp.TagID = extSA.AdUnitID - return nil + if extSA.SeatID != "" { + seatID = extSA.SeatID + } + + return seatID, nil } diff --git a/adapters/seedingAlliance/seedingAlliance_test.go b/adapters/seedingAlliance/seedingAlliance_test.go index 6946ead03ca..c6a5cfb1949 100644 --- a/adapters/seedingAlliance/seedingAlliance_test.go +++ b/adapters/seedingAlliance/seedingAlliance_test.go @@ -5,16 +5,16 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderSeedingAlliance, config.Adapter{ - Endpoint: "https://mockup.seeding-alliance.de/", + Endpoint: "https://mockup.seeding-alliance.de/?ssp={{.AccountID}}", }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -134,20 +134,26 @@ func TestGetMediaTypeForBid(t *testing.T) { } } -func TestAddTagID(t *testing.T) { +func TestGetExtInfo(t *testing.T) { + type args struct { + adUnitId string + seatId string + } tests := []struct { - name string - want string - data string - wantErr bool + name string + expectedAdUnitID string + expectedSeatID string + data args + wantErr bool }{ - {"regular case", "abc123", "abc123", false}, - {"nil case", "", "", false}, - {"unmarshal err case", "", "", true}, + {"regular case", "abc123", "pbs", args{adUnitId: "abc123"}, false}, + {"nil case", "", "pbs", args{adUnitId: ""}, false}, + {"unmarshal err case", "", "pbs", args{adUnitId: ""}, true}, + {"seatId case", "abc123", "seat1", args{adUnitId: "abc123", seatId: "seat1"}, false}, } for _, test := range tests { - extSA, err := json.Marshal(openrtb_ext.ImpExtSeedingAlliance{AdUnitID: test.data}) + extSA, err := json.Marshal(openrtb_ext.ImpExtSeedingAlliance{AdUnitID: test.data.adUnitId, SeatID: test.data.seatId}) if err != nil { t.Fatalf("unexpected error %v", err) } @@ -162,16 +168,20 @@ func TestAddTagID(t *testing.T) { } ortbImp := openrtb2.Imp{Ext: extBidder} - - if err := addTagID(&ortbImp); err != nil { + seatID, err := getExtInfo(&ortbImp) + if err != nil { if test.wantErr { continue } t.Fatalf("unexpected error %v", err) } - if test.want != ortbImp.TagID { - t.Fatalf("want: %v, got: %v", test.want, ortbImp.TagID) + if test.expectedAdUnitID != ortbImp.TagID { + t.Fatalf("want: %v, got: %v", test.expectedAdUnitID, ortbImp.TagID) + } + + if test.expectedSeatID != seatID { + t.Fatalf("want: %v, got: %v", test.expectedSeatID, seatID) } } } diff --git a/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json index 38131eed2a1..b59abb0c7b4 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json +++ b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.seeding-alliance.de/", + "uri": "https://mockup.seeding-alliance.de/?ssp=pbs", "body": { "cur": [ "EUR" diff --git a/adapters/suntContent/suntContenttest/exemplary/banner.json b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner_with_seat.json similarity index 95% rename from adapters/suntContent/suntContenttest/exemplary/banner.json rename to adapters/seedingAlliance/seedingAlliancetest/exemplary/banner_with_seat.json index 153ba0dacde..2dc39865525 100644 --- a/adapters/suntContent/suntContenttest/exemplary/banner.json +++ b/adapters/seedingAlliance/seedingAlliancetest/exemplary/banner_with_seat.json @@ -24,7 +24,8 @@ }, "ext": { "bidder": { - "adUnitId": "example-tag-id" + "adUnitId": "example-tag-id", + "seatId": "123" } } } @@ -33,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", + "uri": "https://mockup.seeding-alliance.de/?ssp=123", "body": { "cur": [ "EUR" @@ -57,7 +58,8 @@ "tagid": "example-tag-id", "ext": { "bidder": { - "adUnitId": "example-tag-id" + "adUnitId": "example-tag-id", + "seatId": "123" } } } diff --git a/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json b/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json index 2b92fb49a0f..ffddaa43da6 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json +++ b/adapters/seedingAlliance/seedingAlliancetest/exemplary/native.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.seeding-alliance.de/", + "uri": "https://mockup.seeding-alliance.de/?ssp=pbs", "body": { "cur": [ "EUR" diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json index 47d4f1f1aad..c79795e48e2 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/invalid_tag_id.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "could not unmarshal openrtb_ext.ImpExtSeedingAlliance: json: cannot unmarshal number into Go struct field ImpExtSeedingAlliance.adUnitID of type string", + "value": "could not unmarshal openrtb_ext.ImpExtSeedingAlliance: json: cannot unmarshal number into Go struct field ImpExtSeedingAlliance.adUnitId of type string", "comparison": "literal" } ], diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json index 55f35ccb0f8..7d1d9527f45 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_bad_request.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.seeding-alliance.de/", + "uri": "https://mockup.seeding-alliance.de/?ssp=pbs", "body": { "cur": [ "EUR" diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json index f51aa12ed5f..465870c712b 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_no_content.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.seeding-alliance.de/", + "uri": "https://mockup.seeding-alliance.de/?ssp=pbs", "body": { "cur": [ "EUR" diff --git a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json index 78d99bc109d..57e7221a872 100644 --- a/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json +++ b/adapters/seedingAlliance/seedingAlliancetest/supplemental/status_not_ok.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.seeding-alliance.de/", + "uri": "https://mockup.seeding-alliance.de/?ssp=pbs", "body": { "cur": [ "EUR" diff --git a/adapters/sharethrough/params_test.go b/adapters/sharethrough/params_test.go index 4edae1246d9..3c2d80dfa8b 100644 --- a/adapters/sharethrough/params_test.go +++ b/adapters/sharethrough/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index f797bfe43d9..7287ca6b86e 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/version" ) var adapterVersion = "10.0" @@ -83,21 +83,29 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E requestCopy.BCat = append(requestCopy.BCat, strImpParams.BCat...) requestCopy.BAdv = append(requestCopy.BAdv, strImpParams.BAdv...) - requestCopy.Imp = []openrtb2.Imp{imp} - - requestJSON, err := json.Marshal(requestCopy) + impressionsByMediaType, err := splitImpressionsByMediaType(&imp) if err != nil { errors = append(errors, err) continue } - requestData := &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: requestJSON, - Headers: headers, + for _, impression := range impressionsByMediaType { + requestCopy.Imp = []openrtb2.Imp{impression} + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errors = append(errors, err) + continue + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + Headers: headers, + } + requests = append(requests, requestData) } - requests = append(requests, requestData) } return requests, errors @@ -150,6 +158,40 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R return bidderResponse, errors } +func splitImpressionsByMediaType(impression *openrtb2.Imp) ([]openrtb2.Imp, error) { + if impression.Banner == nil && impression.Video == nil && impression.Native == nil { + return nil, &errortypes.BadInput{Message: "Invalid MediaType. Sharethrough only supports Banner, Video and Native."} + } + + if impression.Audio != nil { + impression.Audio = nil + } + + impressions := make([]openrtb2.Imp, 0, 3) + + if impression.Banner != nil { + impCopy := *impression + impCopy.Video = nil + impCopy.Native = nil + impressions = append(impressions, impCopy) + } + + if impression.Video != nil { + impCopy := *impression + impCopy.Banner = nil + impCopy.Native = nil + impressions = append(impressions, impCopy) + } + + if impression.Native != nil { + impression.Banner = nil + impression.Video = nil + impressions = append(impressions, *impression) + } + + return impressions, nil +} + func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.Ext != nil { diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index 4aab8fc56cc..2983487ac40 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -3,9 +3,9 @@ package sharethrough import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json b/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json new file mode 100644 index 00000000000..fcebf79acc8 --- /dev/null +++ b/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json @@ -0,0 +1,362 @@ +{ + "mockBidRequest": { + "id": "parent-id", + "tmax": 3000, + "imp": [ + { + "id": "impression-id", + "ext": { + "bidder": { + "pkey": "pkey1" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "native": { + "ver": "1.2", + "request": "placeholder request" + }, + "audio": { + "mimes": [ + "audio/mp4" + ], + "protocols": [ + 1, + 2 + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "parent-id", + "tmax": 3000, + "imp": [ + { + "id": "impression-id", + "tagid": "pkey1", + "ext": { + "bidder": { + "pkey": "pkey1" + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + }, + "source": { + "ext": { + "version": "", + "str": "10.0" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "parent-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + }, + { + "expectedRequest": { + "uri": "http://whatever.url", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "parent-id", + "tmax": 3000, + "imp": [ + { + "id": "impression-id", + "tagid": "pkey1", + "ext": { + "bidder": { + "pkey": "pkey1" + } + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + }, + "source": { + "ext": { + "version": "", + "str": "10.0" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "parent-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + } + ] + } + ] + } + } + }, + { + "expectedRequest": { + "uri": "http://whatever.url", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "parent-id", + "tmax": 3000, + "imp": [ + { + "id": "impression-id", + "tagid": "pkey1", + "ext": { + "bidder": { + "pkey": "pkey1" + } + }, + "native": { + "ver": "1.2", + "request": "placeholder request" + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "https://some-site.com", + "ref": "https://some-site.com" + }, + "device": { + "w": 1200, + "h": 900 + }, + "source": { + "ext": { + "version": "", + "str": "10.0" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "parent-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "native" + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "parent-id", + "impid": "impression-id", + "crid": "some-creative-id", + "adm": "
Ad
", + "price": 20, + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/silvermob/params_test.go b/adapters/silvermob/params_test.go index 13009f6a08b..d69171ed78d 100644 --- a/adapters/silvermob/params_test.go +++ b/adapters/silvermob/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the silvermob schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index 9130ab4d96a..eaa45cb4be0 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -7,17 +7,21 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SilverMobAdapter struct { endpoint *template.Template } +func isValidHost(host string) bool { + return host == "eu" || host == "us" || host == "apac" +} + // Builder builds a new instance of the SilverMob adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { template, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -121,8 +125,14 @@ func (a *SilverMobAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.Ext } func (a *SilverMobAdapter) buildEndpointURL(params *openrtb_ext.ExtSilverMob) (string, error) { - endpointParams := macros.EndpointTemplateParams{ZoneID: params.ZoneID, Host: params.Host} - return macros.ResolveMacros(a.endpoint, endpointParams) + if isValidHost(params.Host) { + endpointParams := macros.EndpointTemplateParams{ZoneID: params.ZoneID, Host: params.Host} + return macros.ResolveMacros(a.endpoint, endpointParams) + } else { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("invalid host %s", params.Host), + } + } } func (a *SilverMobAdapter) MakeBids( diff --git a/adapters/silvermob/silvermob_test.go b/adapters/silvermob/silvermob_test.go index 5b7d60e2ead..ce08651ff59 100644 --- a/adapters/silvermob/silvermob_test.go +++ b/adapters/silvermob/silvermob_test.go @@ -3,9 +3,9 @@ package silvermob import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-host.json b/adapters/silvermob/silvermobtest/supplemental/invalid-host.json new file mode 100644 index 00000000000..f8b2cd3442f --- /dev/null +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-host.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "host": "some_host", + "zoneid": "0" + } + } + } + ] + }, + "httpCalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "invalid host some_host", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/devicetype.go b/adapters/silverpush/devicetype.go new file mode 100644 index 00000000000..a44464048c2 --- /dev/null +++ b/adapters/silverpush/devicetype.go @@ -0,0 +1,46 @@ +package silverpush + +import ( + "regexp" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +var isValidMobile = regexp.MustCompile(`(ios|ipod|ipad|iphone|android)`) +var isCtv = regexp.MustCompile(`(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)`) +var isIos = regexp.MustCompile(`(iPhone|iPod|iPad)`) + +func isMobile(ua string) bool { + return isValidMobile.MatchString(strings.ToLower(ua)) +} + +func isCTV(ua string) bool { + return isCtv.MatchString(strings.ToLower(ua)) +} + +// isValidEids checks for valid eids. +func isValidEids(eids []openrtb2.EID) bool { + for i := 0; i < len(eids); i++ { + if len(eids[i].UIDs) > 0 && eids[i].UIDs[0].ID != "" { + return true + } + } + return false +} + +func getOS(ua string) string { + if strings.Contains(ua, "Windows") { + return "Windows" + } else if isIos.MatchString(ua) { + return "iOS" + } else if strings.Contains(ua, "Mac OS X") { + return "macOS" + } else if strings.Contains(ua, "Android") { + return "Android" + } else if strings.Contains(ua, "Linux") { + return "Linux" + } else { + return "Unknown" + } +} diff --git a/adapters/silverpush/params_test.go b/adapters/silverpush/params_test.go new file mode 100644 index 00000000000..2a20aa2ff71 --- /dev/null +++ b/adapters/silverpush/params_test.go @@ -0,0 +1,65 @@ +package silverpush + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// This file intends to test static/bidder-params/silverpush.json + +// These also validate the format of the external API: request.imp[i].bidRequestExt.silverpush + +// TestValidParams makes sure that the Smaato schema accepts all imp.bidRequestExt fields which Smaato supports. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSilverPush, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected silverpush params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the Smaato schema rejects all the imp.bidRequestExt fields which are not support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSilverPush, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisherId":"test-id-1234-silverpush","bidfloor": 0.05}`, + `{"publisherId":"test123","bidfloor": 0.05}`, + `{"publisherId":"testSIlverpush","bidfloor": 0.05}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"bidfloor": "1123581321"}`, + `{"publisherId":false}`, + `{"bidfloor":false}`, + `{"publisherId":0,"bidfloor": 1123581321}`, + `{"publisherId":false,"bidfloor": true}`, + `{"instl": 0}`, + `{"secure": 0}`, + `{"bidfloor": "1123581321"}`, + `{"publisherId":{}}`, +} diff --git a/adapters/silverpush/silverpush.go b/adapters/silverpush/silverpush.go new file mode 100644 index 00000000000..805ef96657a --- /dev/null +++ b/adapters/silverpush/silverpush.go @@ -0,0 +1,327 @@ +package silverpush + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +const ( + bidderConfig = "sp_pb_ortb" + bidderVersion = "1.0.0" +) + +type adapter struct { + bidderName string + endpoint string +} + +type SilverPushImpExt map[string]json.RawMessage + +type SilverPushReqExt struct { + PublisherId string `json:"publisherId"` + BidFloor float64 `json:"bidfloor"` +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + imps := request.Imp + var errors []error + requests := make([]*adapters.RequestData, 0, len(imps)) + + for _, imp := range imps { + impsByMediaType := impressionByMediaType(&imp) + + request.Imp = []openrtb2.Imp{impsByMediaType} + + if err := validateRequest(request); err != nil { + errors = append(errors, err) + continue + } + + requestData, err := a.makeRequest(request) + if err != nil { + errors = append(errors, err) + continue + } + + requests = append(requests, requestData) + + } + return requests, errors +} + +func (a *adapter) makeRequest(req *openrtb2.BidRequest) (*adapters.RequestData, error) { + reqJSON, err := json.Marshal(req) + if err != nil { + return nil, err + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, nil +} + +func validateRequest(req *openrtb2.BidRequest) error { + imp := &req.Imp[0] + var silverPushExt openrtb_ext.ImpExtSilverpush + if err := setPublisherId(req, imp, &silverPushExt); err != nil { + return err + } + if err := setUser(req); err != nil { + return err + } + + setDevice(req) + + if err := setExtToRequest(req, silverPushExt.PublisherId); err != nil { + return err + } + return setImpForAdExchange(imp, &silverPushExt) +} + +func setDevice(req *openrtb2.BidRequest) { + if req.Device == nil { + return + } + deviceCopy := *req.Device + if len(deviceCopy.UA) == 0 { + return + } + deviceCopy.OS = getOS(deviceCopy.UA) + if isMobile(deviceCopy.UA) { + deviceCopy.DeviceType = 1 + } else if isCTV(deviceCopy.UA) { + deviceCopy.DeviceType = 3 + } else { + deviceCopy.DeviceType = 2 + } + + req.Device = &deviceCopy +} + +func setUser(req *openrtb2.BidRequest) error { + var extUser openrtb_ext.ExtUser + var userExtRaw map[string]json.RawMessage + + if req.User != nil && req.User.Ext != nil { + if err := json.Unmarshal(req.User.Ext, &userExtRaw); err != nil { + return &errortypes.BadInput{Message: "Invalid user.ext."} + } + if userExtDataRaw, ok := userExtRaw["data"]; ok { + if err := json.Unmarshal(userExtDataRaw, &extUser); err != nil { + return &errortypes.BadInput{Message: "Invalid user.ext.data."} + } + var userCopy = *req.User + if isValidEids(extUser.Eids) { + userExt, err := json.Marshal( + &openrtb2.User{ + EIDs: extUser.Eids, + }) + if err != nil { + return &errortypes.BadInput{Message: "Error in marshaling user.eids."} + } + + userCopy.Ext = userExt + req.User = &userCopy + } + } + } + return nil +} + +func setExtToRequest(req *openrtb2.BidRequest, publisherID string) error { + record := map[string]string{ + "bc": bidderConfig + "_" + bidderVersion, + "publisherId": publisherID, + } + reqExt, err := json.Marshal(record) + if err != nil { + return err + } + req.Ext = reqExt + return nil +} + +func setImpForAdExchange(imp *openrtb2.Imp, impExt *openrtb_ext.ImpExtSilverpush) error { + if impExt.BidFloor == 0 { + if imp.Banner != nil { + imp.BidFloor = 0.05 + } else if imp.Video != nil { + imp.BidFloor = 0.1 + } + } else { + imp.BidFloor = impExt.BidFloor + } + + if imp.Banner != nil { + bannerCopy, err := setBannerDimension(imp.Banner) + if err != nil { + return err + } + imp.Banner = bannerCopy + } + + if imp.Video != nil { + videoCopy, err := checkVideoDimension(imp.Video) + if err != nil { + return err + } + imp.Video = videoCopy + } + + return nil +} + +func checkVideoDimension(video *openrtb2.Video) (*openrtb2.Video, error) { + videoCopy := *video + if videoCopy.MaxDuration == 0 { + videoCopy.MaxDuration = 120 + } + if videoCopy.MaxDuration < videoCopy.MinDuration { + videoCopy.MaxDuration = videoCopy.MinDuration + videoCopy.MinDuration = 0 + } + if videoCopy.API == nil || videoCopy.MIMEs == nil || videoCopy.Protocols == nil || videoCopy.MinDuration < 0 { + return nil, &errortypes.BadInput{Message: "Invalid or missing video field(s)"} + } + return &videoCopy, nil +} + +func setBannerDimension(banner *openrtb2.Banner) (*openrtb2.Banner, error) { + if banner.W != nil && banner.H != nil { + return banner, nil + } + if len(banner.Format) == 0 { + return banner, &errortypes.BadInput{Message: "No sizes provided for Banner."} + } + bannerCopy := *banner + bannerCopy.W = openrtb2.Int64Ptr(banner.Format[0].W) + bannerCopy.H = openrtb2.Int64Ptr(banner.Format[0].H) + + return &bannerCopy, nil +} + +func setPublisherId(req *openrtb2.BidRequest, imp *openrtb2.Imp, impExt *openrtb_ext.ImpExtSilverpush) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + if err := json.Unmarshal(bidderExt.Bidder, impExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + if impExt.PublisherId == "" { + return &errortypes.BadInput{Message: "Missing publisherId parameter."} + } + if req.Site != nil { + siteCopy := *req.Site + if siteCopy.Publisher == nil { + siteCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} + } else { + publisher := *siteCopy.Publisher + publisher.ID = impExt.PublisherId + siteCopy.Publisher = &publisher + } + req.Site = &siteCopy + + } else if req.App != nil { + appCopy := *req.App + if appCopy.Publisher == nil { + appCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} + } else { + publisher := *appCopy.Publisher + publisher.ID = impExt.PublisherId + appCopy.Publisher = &publisher + } + appCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} + req.App = &appCopy + + } + + return nil +} + +func impressionByMediaType(imp *openrtb2.Imp) openrtb2.Imp { + impCopy := *imp + if imp.Banner != nil { + impCopy.Video = nil + } + if imp.Video != nil { + impCopy.Banner = nil + + } + return impCopy +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + var bidResp openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) + + // overrride default currency + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: getMediaTypeForImp(sb.Bid[i]), + }) + } + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for. +// SilverPush doesn't support multi-type impressions. +// If both banner and video exist, take banner as we do not want in-banner video. +func getMediaTypeForImp(bid openrtb2.Bid) openrtb_ext.BidType { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo + default: + return "" + } +} + +// Builder builds a new instance of the silverpush adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + bidderName: string(bidderName), + } + return bidder, nil +} diff --git a/adapters/silverpush/silverpush_test.go b/adapters/silverpush/silverpush_test.go new file mode 100644 index 00000000000..630539c0697 --- /dev/null +++ b/adapters/silverpush/silverpush_test.go @@ -0,0 +1,59 @@ +package silverpush + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSilverPush, config.Adapter{ + Endpoint: "http://localhost:8080/bidder/?identifier=5krH8Q"}, config.Server{}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "silverpushtest", bidder) +} + +func TestResponseWithCurrencies(t *testing.T) { + assertCurrencyInBidResponse(t, "USD", nil) + + currency := "USD" + assertCurrencyInBidResponse(t, "USD", ¤cy) + currency = "EUR" + assertCurrencyInBidResponse(t, "EUR", ¤cy) +} + +func assertCurrencyInBidResponse(t *testing.T, expectedCurrency string, currency *string) { + bidder, buildErr := Builder(openrtb_ext.BidderOpenx, config.Adapter{ + Endpoint: "http://rtb.openx.net/prebid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + prebidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, + } + mockedBidResponse := &openrtb2.BidResponse{} + if currency != nil { + mockedBidResponse.Cur = *currency + } + body, _ := json.Marshal(mockedBidResponse) + responseData := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + bidResponse, errs := bidder.MakeBids(prebidRequest, nil, responseData) + + if errs != nil { + t.Fatalf("Failed to make bids %v", errs) + } + assert.Equal(t, expectedCurrency, bidResponse.Currency) +} diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-bidfloor-zero.json b/adapters/silverpush/silverpushtest/exemplary/banner-bidfloor-zero.json new file mode 100644 index 00000000000..a4d73374a42 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-bidfloor-zero.json @@ -0,0 +1,255 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123" + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123" + + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-device-ctv-.json b/adapters/silverpush/silverpushtest/exemplary/banner-device-ctv-.json new file mode 100644 index 00000000000..037b844c519 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-device-ctv-.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (X11; smarttv Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Linux", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (X11; smarttv Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-device-site.json b/adapters/silverpush/silverpushtest/exemplary/banner-device-site.json new file mode 100644 index 00000000000..f12af4b3738 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-device-site.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Linux", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-eids.json b/adapters/silverpush/silverpushtest/exemplary/banner-eids.json new file mode 100644 index 00000000000..62273801292 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-eids.json @@ -0,0 +1,285 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "data": { + "eids": [{ + "source":"pubcid.org", + "uids":[ + { + "id":"01EAJWWNEPN3CYMM5N8M5VXY22", + "atype":1 + } + ] + }] + } + + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + + "eids": [{ + "source":"pubcid.org", + "uids":[ + { + "id":"01EAJWWNEPN3CYMM5N8M5VXY22", + "atype":1 + } + ] + }] + + + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-iOS-ua.json b/adapters/silverpush/silverpushtest/exemplary/banner-iOS-ua.json new file mode 100644 index 00000000000..4a8868ce7fa --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-iOS-ua.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (iPhone12,1; U; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "iOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (iPhone12,1; U; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-windows-ua.json b/adapters/silverpush/silverpushtest/exemplary/banner-windows-ua.json new file mode 100644 index 00000000000..7e6eb647c27 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-windows-ua.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; RM-1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Mobile Safari/537.36 Edge/15.15254", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Windows", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; RM-1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Mobile Safari/537.36 Edge/15.15254", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-without-site-publisher.json b/adapters/silverpush/silverpushtest/exemplary/banner-without-site-publisher.json new file mode 100644 index 00000000000..042dd9c5021 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-without-site-publisher.json @@ -0,0 +1,244 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "site": { + "domain": "stg.betterbutter.in" + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner-without-w-h.json b/adapters/silverpush/silverpushtest/exemplary/banner-without-w-h.json new file mode 100644 index 00000000000..a3764658c03 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner-without-w-h.json @@ -0,0 +1,278 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + }] + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 290, + "h": 260, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + }] + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner.json b/adapters/silverpush/silverpushtest/exemplary/banner.json new file mode 100644 index 00000000000..8476f805716 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner.json @@ -0,0 +1,257 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/banner_without_publisher.json b/adapters/silverpush/silverpushtest/exemplary/banner_without_publisher.json new file mode 100644 index 00000000000..2fd4188f4a5 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/banner_without_publisher.json @@ -0,0 +1,242 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/video-bidfloor-zero.json b/adapters/silverpush/silverpushtest/exemplary/video-bidfloor-zero.json new file mode 100644 index 00000000000..d9650cd0e8a --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/video-bidfloor-zero.json @@ -0,0 +1,247 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 1, + "maxduration": 0, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123" + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 1, + "maxduration": 120, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123" + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + { + "bid": { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + }, + "type":"video" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/video.json b/adapters/silverpush/silverpushtest/exemplary/video.json new file mode 100644 index 00000000000..2f0dccef05a --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/video.json @@ -0,0 +1,250 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 1, + "maxduration": 0, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 1, + "maxduration": 120, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + { + "bid": { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + }, + "type":"video" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/exemplary/video_min_max_duration.json b/adapters/silverpush/silverpushtest/exemplary/video_min_max_duration.json new file mode 100644 index 00000000000..d0ac6889362 --- /dev/null +++ b/adapters/silverpush/silverpushtest/exemplary/video_min_max_duration.json @@ -0,0 +1,250 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 10, + "maxduration": 5, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + "mimes": [ + "video/mp4" + ], + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + + "maxduration": 10, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + { + "bid": { + "id": "iJQDNZRA", + "impid": "1", + "price": 0.04, + "burl": "https://localhost:8181/tracker/?p=iEUrKGOlRpwuy6LMCoEc8oew34TcCljlTEjAN1URYhXSxosy0IanKUQw36mX8Igm5v5887kijBb_kwdRT8OIJv1KQWLtEYJFkSJOdZJvSFNF_6Ir6_yaslz3Br4JuIUC9owtY46bQy0ltarrzOf8Xhq5XosFj88AVue5y5aBCxvdNF0nqtk1f_IN5h59yNBlUGTQ6_hAxOO_f27g3t1sjDpoojfZ5gcj2P3K3PN5uVB5IFU-xUV84jil_CYyXLuA1v9kG2DH0Y1ypDZ7LUSSX0rGlH-XUX22hWFue0ENIsvoAqk5FPYXmfmIAWH6JpJ6_mHQdC9IP_gXOiXqXL2HLrFHWeKkXqb7by6UzoptT8Ro60EHzWd5VfmhAUv42UExriCZRW8YRW_LAFCFlauvRcwCL-lv8DsrbUgj1oPbxI_G9Z9q-8o_qqAqU9dwXlRgIboANc69AOX3JGtCKxfxlAEAVjrONzgO9LNyDHbUUv5oT7fv3HEgf6UQcbzbyQ8ISo1rq48ypIA9RWCb9hFmkjxUsIZJc0ScL3QHIdoXxxJ3Q0Zu7MgctSYMoCn77gsNr57HQJLsgXJDi53AgMsxdmHpj64kJTVoemRHpvbPv3REHAGXcAfixF0XQOEj-uDP9SDQ_ZXI-LaFbpdYyX6GWqQtpOnLjK4FIg6nKZD_W7MnLw6TWqh7o3BV-uouOmExvHgU2bbMpAtcDEVYL0rVh1jBIpSLk3UEZuidovj3TlnYVXG07hmJb-jsRzMplAK3UqPTaCa-3RrA8gklQTiampKoryI5G5dijCOTp0kYSWcYh50cqzG9pXqkH-73y3aeYa-RauCIJdFnq-17kfImdVCNSBELe_uW-dTDUUFZ22i8f5C_Ifn9xnGlqMUrq9AEJ2OOmuKgMJ0IuFhTqD18V4HhojWstF-O3B4_UyXte-ZN598PCFXkg_Uc8gsP5yQ3sFLMOWv-HDfuGnLDxDvT8T-yjUjRAem0IlaCsECR3eaAhSrP8yVe7OzFdRDYo-ZnZq_KW97U9aMTy7xUvoRP_fLGpVH0KLxjzEr9dTrQContTAu8knYWE0vcWRJZbPm28mzBVMwtXp8HMEi1-afUhJyhgkvRYRCaKUqyP3n3OAXN-5zggxUtH4tnvpy6Ew1xkBIQ9aFjWwJZRaE7-t1HhVjtXtyyQmPE6U-qax8IWxUqNE5cLi2J2Avjh5S69yfOOXwzWe_HDT7jFeCA8YLsx9G5iUFQpcvbnGlb4RWfTgLzpALlpssAT-sd33_zXXQ0oWvbsgOPibMmt5g7ggbNCy9J9onz2DE_CMjm6SySPS1l0eIpl3vB8dYgNt-e2Wowj7FrATYKLuVa_2VQY9juIOLyGd_VB1rrayJdliWzidzitZXbiqWbl6-XLLh28x7b3OheTRG_SWC3opXSiA2r4aTc3VvqAeMo2XtflWIuLjt7jvgtH_TwqR3oli-9bD1UeTT9AaWJRfS6xmMNlqbHw3fKXwTKGJEMQkyIkiXY4vOcMUYJBfaRkQbKRJ5wRGw3FLhgE56oSAhEPnjPZDx69-toWOziSJGWKR4p4iAaLNh2Xoeyi8Y9yjId7u07OwKNsOUqYo8GaF4hgA6kkrfofB1064vi2-_AUqGch4WFZQ5CyOyT3_FfKXxqR5HrHWXevT8zlXX4dFmiMt3rI5riojhVjMW9XwxUJFkvKW-n0SWPgx7phnT0Rew0WcY-pp86g04AjqxJ-WBXCYpR6z1_No6znnkPm3zaPKLDUgffx7ImRGfNxoIQGUQ5CV9P2v8W2mBaENDjDMuQh09wG0q7y0ttvj9PtqyRtYwB_VhxX5iiFl6d2No5IIJIpUK5QqykI06pWDU_7M4qoIKLwGgc8ICdUqqy3UgZZZxWnyuwuJP0FNgzjKxnZE8PBrtY-m9CSU_UYSuMtUw7cE1XsCvenDrGgj32OLjVoKkp8IrULovZxnSHhV8Cg0Q6mgtDmSuVGc4cqA_nvzK6JwGhAN1Zn2dY3xcdjt5HUqK9SxGMiQUM3yEtX5BsyBqyGmP8m8PBEtscoZpQ-qvDHEUpk7VIuimUhnDlzaFdSzMwMmEBMabHu5tBau5aU4eUmCxCWVrndeKlszgDqbuOgz6sJ5xGoVcmBR2ri80B_d_2ZkOZhq0_X9Dyisyb-ggYNLbUuoFYemVSqJ67pEnVILZRqUrlgWN-xQ6FTshQfLoAWFetUK-89bAAi8Ly76plHomz4NYGqWhfOz9ot09ivybHVY66BtxLYYGwcVeXLB2y3cqQ94Pyo7B-ixe6IQQOvWP0CO6hgkwNqYqThQaU8VEQQR2ehprcE0uE2t4bqLlsuNEZfrqGJx8UNxfoflVZXx2hqdmQN_r0-vQwjAXKvbElOkJZP0Yvs3Vf-Ym0UocU2a51_iQklxvpDqMVW4dmRCbvUcIcKmXX2C8xwqYg5z9SzKzyYqUZjvQZJ0xrF_T-uiQjKQAUxTxWI9K39xV-OL_KlTlR8OqSUrjzQS1mSloLLMFuQvctujaf0cH1a6yqGHUaUkHwTO0UvzIzR_-4LEmiGJSPQP0qDyqjr2RxqbRZRAgnjOMjjo9dZc4mf_2ywdeC2KAS56gtr8hfDI0lp7R6fIVLfrDAL2PzqIERjqoEyP3WAuqjsBiVw-tui3O1Te4tliQ7IhvLjeaG1LvuvbfI1z7A&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "\n\n \n \n DBM\n In-Stream Video\n \n \n\n\n\n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n IN\n 4\n 6260\n 0.0400USD\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 485459443-1\n Yes\n \n \n \n Silverpush-1687361105\n \n\n", + "adomain": [ + "disney.com" + ], + "cid": "sEJAtadBdhxs", + "crid": "485459443", + "w": 360, + "h": 727, + "mtype":2, + "ext": { + "origbidcpm": 0.04, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "video" + }, + "third_party_buyer_token": "AKAmf-CsVHgbcp3N0PM2mbsEk1oxApVbS1Hirp9rrNZ-_AFPCptW6_0kFrISwByjI8QMGTsTLgEB6Wq-fmTmi--xzsoyp4oME_pWeNmkLm0Na81xc7ZVv90IF3Pe1kZszpASOYUkxSXH" + } + }, + "type":"video" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-banner-format.json b/adapters/silverpush/silverpushtest/supplemental/bad-banner-format.json new file mode 100644 index 00000000000..78a7d09dbc9 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-banner-format.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "88de601e-3d98-48e7-81d7-00000000" + } + ] + }] + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No sizes provided for Banner.", + "comparison": "literal" + } + ] + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-eids-data.json b/adapters/silverpush/silverpushtest/supplemental/bad-eids-data.json new file mode 100644 index 00000000000..d813bdf0bb0 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-eids-data.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": {"data":9} + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid user.ext.data.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-eids-value.json b/adapters/silverpush/silverpushtest/supplemental/bad-eids-value.json new file mode 100644 index 00000000000..f91b0fc7cce --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-eids-value.json @@ -0,0 +1,278 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "" + } + ] + }] + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 290, + "h": 260, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": { + "eids": [ + { + "source": "amxid", + "uids": [ + { + "atype": 1, + "id": "" + } + ] + }] + } + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "seatbid": [ + { + "bid": [ + { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + } + ], + "seat": "silverpush" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "bids": [ + {"bid": { + "id": "TbthOMFy", + "impid": "1", + "price": 0.03, + "burl": "https://localhost:8181/tracker/?p=FAlHYmO6o9Zl4J-A9I0lpSo19KeQaWQU_nIsE-kC1tevBA0xn9BSCf1FxK6f-zkPdD88g5V1wAwJnsj3HFHsQMoWLEv7j-DOqwSR_qtcTs77QhThKsEptZTR1BfmIaiBzNK3PCqdbUzSAdcUv8ftAZkzKBpRcoZuFYUFiSCRq014TN8AOhmgartsPmNuex_hS9Zq4Px-SsZkbGzL9MoBNfygT8v-eIC7lgDdKIlhUfcTOwHqcVx2Ji3vHQieaBDBlw2GTAATu1KEwLfS2hqzh0XQEI-TFBjvrvEypqfjQMdTtUcS1N34Pc7WPPgVbJk4X2p8DeoTBpKpCQJHRvveYUvC46WySfNha1lv_MB0Mf1hadYrDDqt3hjZa0o-7wRZsjGutVf2bM-I4LUSSNMCTNJF48ooJ_cZMd6Z0GWWQGZpM5yG5qUBJKtvN4ZbHTNftqyspsy_XyMD9c2Bl4QZA3Hn-McV0mQ_eelyCg-2vtLCDEOq4BsqhPNHy5jUsIC-kJYSng1LM_sAi6SAVhLWj0cH7hTGqPNhvTevCfd6o-B1v4ynptVnDmmBT7Mz4pDNWSYiJapg9QnBwjmENG1hbTtrxQoSVCXhhWgjsoQOB6XA_RdHmGqqQ4MgSn0pjKi--jnKCloXykuh3CM8a8PGzbNRk8aCHucTA-g12bmM5xsZg08hTW44eB-CJ7DHltjSc8FuDG2866YSXWahG488QWYmS-_AEBMSV2mIk49BWOWSOfjDK0Wo0_k9FqaoPpB-rtdsUMo-0VWLlZInDNJ1-BZgGC0CKx3BgYTonAoKxU5w86OayNr_kOGHzFKMMt2a7d6lTZmPz-x8ndFIYzsrzLMLxvvwLs2FZnHhobmPHgwDCO94QhrHtHC31edqa2T3QnlBnEg2fovlzi44a2vItNIZYYdhxlGKvfW4BwgPVc0BQVxl9voP-p4cytPrstWODIZ_2D6HiU1vdlZyXBFoPaeovOHE5Nvf1n9IQOlDRmp-7tE3Nx6iS_jiqnGZYIFKSwOGL7gUyoeYKZHc7tN_XUg6cxzBTf1ERC7C5StsLs7VCXCqWZg0t-RSeCdQ4zLt2eZEB_60KpW9RtXo5aALR49OsBLrXe2gldLKMwm9Waa6LZC1R5hj8T4gnpvjEC9jLyFEt7TmVRDkXWeXYiKW2cZCpZjPxv8S5cb0FPliR4tT3lkkOoRhlP9yWiVoHp58CGcfNonBvvwG9rMUdAqqVJD7Agw-v6jpLl_v3nk5ujdD09YT8Da6peM5m5J3Qmm7eTyUsm8zNroHeDSkcUh6kkpvUbrpvaFNaefIw1e1kzR7ONG9SWmpULSIygX4Ibh6FXRbyXw3Ekx-UQLS2EZ2b-MCuL08lyjGioRnDk8zB5jZo-0DKql93CbtDnjJbV-Q6a3Zy0L3zpyCx4V6mnd5kpe7USU-am-dy0aSAgNWkERkcc-v2l4oNVn9gLG5C5L7CS_xA1gceds34LfKFKFn0PwCZglpDUoOk1PZUWY6dQbTnC3qpK4_quEZxU4gZxoMlzsD1DiyX-RtXfqc-vUeKCVUzfz4IaL4BWizOh5c_YNdUib6d70DXGm_eKIBMVrSFGKuXelo2g6QsfosDV-qxnqhkwaFiAVzGFfrRYj5DYVHJHsr4TJ-7_c-OMX5ncr_3FiFHUk3ZVHVOUxTW3jfgWnnHSoPDmy9M2FAcbFQnbgnp9TB_Zmw7xxZXrzXvZuAbFbDqN34hIhqdz-U8I4KfituMY1HCCpgz7wCh6etG6868-g5wu6BRR7qv4F5ID4TEZzQOKodx-Hy5hJcBPwgphMMt6Ch7q_v4Hur-e-JMoJhjzsatxSc5klv-ZrJF6GkfMchMPwHYtfIyBcEKCQHKZAMsaJN20EhRcmNjMPS1thFqN4TXQSPVD__j4yn_KF175Xz7VAnIzfoFW9NiE9_trtZGPiJ&wn_wp=${AUCTION_PRICE}&wn_aid=${AUCTION_ID}&wn_abi=${AUCTION_BID_ID}&wn_aii=${AUCTION_IMP_ID}&wn_aai=${AUCTION_AD_ID}&wn_asi=${AUCTION_SEAT_ID}", + "adm": "https://adrta.com/i?clid=slp&paid=choc&avid=2008&caid=&plid=cr39487593745jefoijdf&publisherId=17281&siteId=&lineItemId=QKWUYk&kv1=360x640&kv2=&kv3=Uyb8g9qJmfcma4ABC&kv7=159&kv10=Optus&kv4=110.33.122.75&kv11=7360132c-cc30-4334-b3fe-c3ec98e27a0e&kv12=3&kv15=AUS&kv26=Android&kv16=-35.342200&kv17=149.210100&kv18=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv19=9e13d2bf-e968-4065-b4db-852123b627a9&kv25=a.f.fnf.mod.returns.rapper.music.funkin.fire&kv27=Mozilla%2F5.0+%28Linux%3B+Android+11%3B+CPH2209+Build%2FRP1A.200720.011%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F100.0.4896.127+Mobile+Safari%2F537.36&kv24=OTT/CTV", + "adomain": [ + "linio.com.co" + ], + "cid": "FmQOmKiRUsLh", + "crid": "cr39487593745jefoijdf", + "w": 360, + "h": 640, + "mtype":1, + "ext": { + "origbidcpm": 0.03, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "silverpush" + }, + "type": "banner" + } + } + }, + "type":"banner" + } + ], + "cur": "USD" + + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext-bidder.json b/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext-bidder.json new file mode 100644 index 00000000000..5bedcfe34c6 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext-bidder.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder":98 + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go value of type openrtb_ext.ImpExtSilverpush", + "comparison": "literal" + } + ] + + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext.json b/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..7c5b7af90e2 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-imp-ext.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": 98 + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal number into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] + + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-publisherId.json b/adapters/silverpush/silverpushtest/supplemental/bad-publisherId.json new file mode 100644 index 00000000000..97f292d670c --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-publisherId.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing publisherId parameter.", + "comparison": "literal" + } + ] + + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-response-unmarshal.json b/adapters/silverpush/silverpushtest/supplemental/bad-response-unmarshal.json new file mode 100644 index 00000000000..c48c48f1bf0 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-response-unmarshal.json @@ -0,0 +1,184 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 2, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "macOS", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 200, + "body": "for unmarshal error" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-user-ext-eids.json b/adapters/silverpush/silverpushtest/supplemental/bad-user-ext-eids.json new file mode 100644 index 00000000000..11922cd54b2 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-user-ext-eids.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC", + "ext": 99 + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + + "expectedMakeRequestsErrors": [ + { + "value": "Invalid user.ext.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/bad-video-format.json b/adapters/silverpush/silverpushtest/supplemental/bad-video-format.json new file mode 100644 index 00000000000..62d330679f2 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/bad-video-format.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "video": { + "api": [ + 1, + 2, + 4, + 6 + ], + + "protocols": [ + 4, + 5, + 6, + 7 + ], + "linearity": 1, + "placement": 1, + "minduration": 0, + "maxduration": 60, + "startdelay": 0, + "w": 320, + "h": 250 + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 3, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or missing video field(s)", + "comparison": "literal" + } + ] + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/status-204-resp.json b/adapters/silverpush/silverpushtest/supplemental/status-204-resp.json new file mode 100644 index 00000000000..fa95a6af1c7 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/status-204-resp.json @@ -0,0 +1,188 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 204, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + + } + } + } + ] + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/status-400-resp.json b/adapters/silverpush/silverpushtest/supplemental/status-400-resp.json new file mode 100644 index 00000000000..1a3c57f6eb4 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/status-400-resp.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 400, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + + } \ No newline at end of file diff --git a/adapters/silverpush/silverpushtest/supplemental/status-500-resp.json b/adapters/silverpush/silverpushtest/supplemental/status-500-resp.json new file mode 100644 index 00000000000..86dff666431 --- /dev/null +++ b/adapters/silverpush/silverpushtest/supplemental/status-500-resp.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest":{ + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "uri": "http://localhost:8080/bidder/?identifier=5krH8Q", + "body": { + + "id": "X4lwEgAAyXAKlqvGCwa9kg", + "imp": [ + { + "id": "1", + "banner": { + "w": 300, + "h": 50, + "pos": 3, + "mimes": [ + "image/jpeg", + "image/gif" + ], + "expdir": [ + 1, + 2, + 3, + 4 + ], + + "format": [ + { + "w": 290, + "h": 260 + } + ] + }, + "tagid": "2612792908", + "bidfloor": 0.05, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "publisherId": "publisher123", + "bidfloor": 0.05 + } + } + } + ], + "app": { + "publisher": { + "id": "publisher123" + } + }, + "device": { + "devicetype": 1, + "dnt": 0, + "h": 2340, + "ifa": "9e13d2bf-e968-4065-b4db-852123b627a9", + "ip": "110.33.122.75", + "js": 1, + "make": "OPPO", + "model": "CPH2209", + "os": "Android", + "osv": "11", + "pxratio": 3, + "ua": "Mozilla/5.0 (Linux; Android 11; CPH2209 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36", + "w": 1080 + }, + "site": { + "domain": "stg.betterbutter.in", + "publisher": { + "id": "publisher123" + } + }, + "user": { + "id": "Uyb8g9qJmfcma4ABC" + }, + "at": 1, + "tmax": 1300, + "cur": [ + "USD" + ], + "ext": { + "bc": "sp_pb_ortb_1.0.0", + "publisherId": "publisher123" + } + } + }, + "mockResponse": { + "status": 500, + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": [ + "2.5" + ] + }, + "body": { + + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + + } \ No newline at end of file diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go index a4dad157bd1..8e601187ccd 100644 --- a/adapters/smaato/image.go +++ b/adapters/smaato/image.go @@ -3,9 +3,10 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" "net/url" "strings" + + "github.com/prebid/prebid-server/v2/errortypes" ) type imageAd struct { diff --git a/adapters/smaato/native.go b/adapters/smaato/native.go index d0d40d35c57..b18a5fc4490 100644 --- a/adapters/smaato/native.go +++ b/adapters/smaato/native.go @@ -3,7 +3,8 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" + + "github.com/prebid/prebid-server/v2/errortypes" ) type nativeAd struct { diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go index 2e29550a394..d1c334acbfa 100644 --- a/adapters/smaato/params_test.go +++ b/adapters/smaato/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file intends to test static/bidder-params/smaato.json diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go index a8865361d38..09e1f2bf3d6 100644 --- a/adapters/smaato/richmedia.go +++ b/adapters/smaato/richmedia.go @@ -3,9 +3,10 @@ package smaato import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/errortypes" "net/url" "strings" + + "github.com/prebid/prebid-server/v2/errortypes" ) type richMediaAd struct { diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index e0892ef50fb..ffdeca06d7a 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -9,12 +9,12 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" ) const clientVersion = "prebid_server_0.6" diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go index a9caf86fe65..0d7d39027ea 100644 --- a/adapters/smaato/smaato_test.go +++ b/adapters/smaato/smaato_test.go @@ -9,10 +9,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/smartadserver/params_test.go b/adapters/smartadserver/params_test.go index ec4a7f7ec6c..fcd07278be6 100644 --- a/adapters/smartadserver/params_test.go +++ b/adapters/smartadserver/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/smartadserver.json diff --git a/adapters/smartadserver/smartadserver.go b/adapters/smartadserver/smartadserver.go index ecc26f9a1cc..ce14533f78b 100644 --- a/adapters/smartadserver/smartadserver.go +++ b/adapters/smartadserver/smartadserver.go @@ -9,10 +9,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SmartAdserverAdapter struct { diff --git a/adapters/smartadserver/smartadserver_test.go b/adapters/smartadserver/smartadserver_test.go index f0a4ae8b48c..be5c13d54e2 100644 --- a/adapters/smartadserver/smartadserver_test.go +++ b/adapters/smartadserver/smartadserver_test.go @@ -3,9 +3,9 @@ package smartadserver import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/smarthub/params_test.go b/adapters/smarthub/params_test.go index 7797d4a82c9..3d9a6c351bc 100644 --- a/adapters/smarthub/params_test.go +++ b/adapters/smarthub/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smarthub/smarthub.go b/adapters/smarthub/smarthub.go index 018f51aabc7..877987dd2e0 100644 --- a/adapters/smarthub/smarthub.go +++ b/adapters/smarthub/smarthub.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/smarthub/smarthub_test.go b/adapters/smarthub/smarthub_test.go index 837d219c9fd..d231f77703a 100644 --- a/adapters/smarthub/smarthub_test.go +++ b/adapters/smarthub/smarthub_test.go @@ -3,9 +3,9 @@ package smarthub import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index ffc9854670c..875d9fc2aac 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Base adapter structure. diff --git a/adapters/smartrtb/smartrtb_test.go b/adapters/smartrtb/smartrtb_test.go index b80955e6401..c0e83d3b826 100644 --- a/adapters/smartrtb/smartrtb_test.go +++ b/adapters/smartrtb/smartrtb_test.go @@ -3,9 +3,9 @@ package smartrtb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smartx/params_test.go b/adapters/smartx/params_test.go new file mode 100644 index 00000000000..81ee269a5ef --- /dev/null +++ b/adapters/smartx/params_test.go @@ -0,0 +1,58 @@ +package smartx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var validParams = []string{ + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986", "siteId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986", "appId":"22860"}`, +} + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderSmartx, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected smartx params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"anyparam": "anyvalue"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A"}`, + `{"publisherId":"11986"}`, + `{"siteId":"22860"}`, + `{"appId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "publisherId":"11986"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "siteId":"22860"}`, + `{"tagId":"Nu68JuOWAvrbzoyrOR9a7A", "appId":"22860"}`, + `{"publisherId":"11986", "appId":"22860"}`, + `{"publisherId":"11986", "appId":"22860"}`, +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSmartHub, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/smartx/smartx.go b/adapters/smartx/smartx.go new file mode 100644 index 00000000000..7b928ec198c --- /dev/null +++ b/adapters/smartx/smartx.go @@ -0,0 +1,90 @@ +package smartx + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpointURL string +} + +func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { + return &adapter{ + endpointURL: config.Endpoint, + }, nil +} + +func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + openRTBRequestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errs = append(errs, fmt.Errorf("marshal bidRequest: %w", err)) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("x-openrtb-version", "2.5") + + if openRTBRequest.Device != nil { + if openRTBRequest.Device.UA != "" { + headers.Set("User-Agent", openRTBRequest.Device.UA) + } + + if openRTBRequest.Device.IP != "" { + headers.Set("Forwarded", "for="+openRTBRequest.Device.IP) + headers.Set("X-Forwarded-For", openRTBRequest.Device.IP) + } + } + + return append(requestsToBidder, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpointURL, + Body: openRTBRequestJSON, + Headers: headers, + }), nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + if len(response.SeatBid) == 0 { + return nil, []error{errors.New("no bidders found in JSON response")} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if response.Cur != "" { + bidResponse.Currency = response.Cur + } + + var errs []error + + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeVideo, + }) + } + } + + return bidResponse, errs +} diff --git a/adapters/smartx/smartx_test.go b/adapters/smartx/smartx_test.go new file mode 100644 index 00000000000..503a547d2fc --- /dev/null +++ b/adapters/smartx/smartx_test.go @@ -0,0 +1,24 @@ +package smartx + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +const testsDir = "smartxtest" +const testsBidderEndpoint = "https://bid.smartclip.net/bid/1005" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderRise, + config.Adapter{Endpoint: testsBidderEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 115, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/smartx/smartxtest/exemplary/01-video.json b/adapters/smartx/smartxtest/exemplary/01-video.json new file mode 100644 index 00000000000..c4d3f9c5b31 --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/01-video.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest":{ + "id":"test-request-id-video", + "imp":[ + { + "id":"test-imp-id", + "video":{ + "mimes":[ + "video/mp4" + ] + } + } + ] + }, + "httpCalls":[ + { + "expectedRequest":{ + "uri":"https://bid.smartclip.net/bid/1005", + "body":{ + "id":"test-request-id-video", + "imp":[ + { + "id":"test-imp-id", + "video":{ + "mimes":[ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"test-request-id-video", + "seatbid":[ + { + "seat":"smartadserver", + "bid":[ + { + "id":"8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid":"test-imp-id-video", + "price":0.500000, + "adm":"some-test-ad", + "crid":"crid_10", + "h":576, + "w":1024, + "mtype":2 + } + ] + } + ], + "cur":"EUR" + } + } + } + ], + "expectedBidResponses":[ + { + "bids":[{ + "bid":{ + "id":"8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid":"test-imp-id-video", + "price":0.500000, + "adm":"some-test-ad", + "crid":"crid_10", + "h":576, + "w":1024, + "mtype":2 + }, + "currency":"EUR", + "type": "video" + }] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/exemplary/02-consent.json b/adapters/smartx/smartxtest/exemplary/02-consent.json new file mode 100644 index 00000000000..bc0e5604f4e --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/02-consent.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "prebid": { + "bidder": { + "smartx": { + "publisherId": "11986", + "tagId": "Nu68JuOWAvrbzoyrOR9a7A", + "siteId": "22860" + } + } + } + } + } + ], + "user": { + "ext": { + "consent": "COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "prebid": { + "bidder": { + "smartx": { + "publisherId": "11986", + "tagId": "Nu68JuOWAvrbzoyrOR9a7A", + "siteId": "22860" + } + } + } + } + } + ], + "user": { + "ext": { + "consent": "COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "ext": { + "ix": {} + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/exemplary/03-device.json b/adapters/smartx/smartxtest/exemplary/03-device.json new file mode 100644 index 00000000000..f45cc76c99a --- /dev/null +++ b/adapters/smartx/smartxtest/exemplary/03-device.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-request-id-video", + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-G780G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/17.0 Chrome/96.0.4664.104 Mobile Safari/537.36", + "geo": { + "lat": 48.1663, + "lon": 11.5683, + "type": 2, + "country": "DEU", + "region": "BY", + "city": "Munich", + "zip": "81249", + "ipservice": 3 + }, + "dnt": 0, + "lmt": 0, + "ip": "0.0.0.0", + "devicetype": 4, + "make": "Samsung", + "model": "SM-G780G", + "os": "Android", + "language": "en" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-request-id-video", + "device": { + "ua": "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-G780G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/17.0 Chrome/96.0.4664.104 Mobile Safari/537.36", + "geo": { + "lat": 48.1663, + "lon": 11.5683, + "type": 2, + "country": "DEU", + "region": "BY", + "city": "Munich", + "zip": "81249", + "ipservice": 3 + }, + "dnt": 0, + "lmt": 0, + "ip": "0.0.0.0", + "devicetype": 4, + "make": "Samsung", + "model": "SM-G780G", + "os": "Android", + "language": "en" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-video", + "seatbid": [ + { + "seat": "smartadserver", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 576, + "w": 1024, + "mtype": 2 + } + ] + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-video", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 576, + "w": 1024, + "mtype": 2 + }, + "currency": "EUR", + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json b/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json new file mode 100644 index 00000000000..d44ef7f77e1 --- /dev/null +++ b/adapters/smartx/smartxtest/supplemental/02-internal-server-error.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-internal-server-error-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-internal-server-error-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json b/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json new file mode 100644 index 00000000000..71875f12809 --- /dev/null +++ b/adapters/smartx/smartxtest/supplemental/03-missing-bidder-in-response.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-missing-bidder-in-response-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.smartclip.net/bid/1005", + "body": { + "id": "test-missing-bidder-in-response-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ] + } + }, + "mockResponse": { + "body": { + "seatbid": [] + }, + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "no bidders found in JSON response", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smartyads/params_test.go b/adapters/smartyads/params_test.go index 3aa5c0e837d..0e1b7186397 100644 --- a/adapters/smartyads/params_test.go +++ b/adapters/smartyads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index c4455a62ccf..ce50e926444 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type SmartyAdsAdapter struct { diff --git a/adapters/smartyads/smartyads_test.go b/adapters/smartyads/smartyads_test.go index b7edf2cadd2..6a697c1d5f4 100644 --- a/adapters/smartyads/smartyads_test.go +++ b/adapters/smartyads/smartyads_test.go @@ -3,9 +3,9 @@ package smartyads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go index 3217cafabab..ac055542417 100644 --- a/adapters/smilewanted/params_test.go +++ b/adapters/smilewanted/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/smilewanted.json diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go index 34ffdf20711..93278c2c25e 100644 --- a/adapters/smilewanted/smilewanted.go +++ b/adapters/smilewanted/smilewanted.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go index 73fe15387c6..3df23f42911 100644 --- a/adapters/smilewanted/smilewanted_test.go +++ b/adapters/smilewanted/smilewanted_test.go @@ -3,9 +3,9 @@ package smilewanted import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sonobi/params_test.go b/adapters/sonobi/params_test.go index 46c31015dae..c84a99edfdf 100644 --- a/adapters/sonobi/params_test.go +++ b/adapters/sonobi/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/sonobi.json diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 2b2a04c27da..f85e6959464 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // SonobiAdapter - Sonobi SonobiAdapter definition diff --git a/adapters/sonobi/sonobi_test.go b/adapters/sonobi/sonobi_test.go index 8e9790bd6f0..3123781323d 100644 --- a/adapters/sonobi/sonobi_test.go +++ b/adapters/sonobi/sonobi_test.go @@ -3,9 +3,9 @@ package sonobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 240224903a4..849e9c0b5d0 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -8,10 +8,10 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) @@ -20,6 +20,11 @@ type SovrnAdapter struct { URI string } +type sovrnImpExt struct { + Bidder openrtb_ext.ExtImpSovrn `json:"bidder"` + AdUnitCode string `json:"adunitcode,omitempty"` +} + func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { headers := http.Header{} headers.Add("Content-Type", "application/json") @@ -74,11 +79,24 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt imp.BidFloor = sovrnExt.BidFloor } + var impExtBuffer []byte + impExtBuffer, err = json.Marshal(&sovrnImpExt{ + Bidder: sovrnExt, + AdUnitCode: sovrnExt.AdUnitCode, + }) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.Ext = impExtBuffer + // Validate video params if appropriate video := imp.Video if video != nil { if video.MIMEs == nil || - video.MinDuration == 0 || video.MaxDuration == 0 || video.Protocols == nil { errs = append(errs, &errortypes.BadInput{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 4a382d9b58e..1e041933e6c 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -3,9 +3,9 @@ package sovrn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sovrn/sovrntest/supplemental/adunitcode.json b/adapters/sovrn/sovrntest/supplemental/adunitcode.json new file mode 100644 index 00000000000..4719752058d --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/adunitcode.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456", + "adunitcode": "sovrn_auc" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "ext": { + "adunitcode": "sovrn_auc", + "bidder": { + "tagid": "123456", + "adunitcode": "sovrn_auc" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/fpd.json b/adapters/sovrn/sovrntest/supplemental/fpd.json index 005d159d740..e4368c13c2b 100644 --- a/adapters/sovrn/sovrntest/supplemental/fpd.json +++ b/adapters/sovrn/sovrntest/supplemental/fpd.json @@ -86,8 +86,7 @@ "tagid": "123456", "ext": { "bidder": { - "tagid": "123456", - "anotherid": "654321" + "tagid": "123456" } } } diff --git a/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json new file mode 100644 index 00000000000..fdb14371336 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "AdUnitCode": "sovrn_auc", + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/video/simple-video.json b/adapters/sovrn/sovrntest/video/simple-video.json index 9cfeef5e11d..fae6778a015 100644 --- a/adapters/sovrn/sovrntest/video/simple-video.json +++ b/adapters/sovrn/sovrntest/video/simple-video.json @@ -10,7 +10,6 @@ "video/3gpp", "video/x-ms-wmv" ], - "minduration": 5, "maxduration": 30, "protocols": [ 4, @@ -79,7 +78,6 @@ "video/3gpp", "video/x-ms-wmv" ], - "minduration": 5, "maxduration": 30, "protocols": [ 4, diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json b/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json deleted file mode 100644 index 88703ddadc4..00000000000 --- a/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4", - "video/3gpp", - "video/x-ms-wmv" - ], - "maxduration": 30, - "protocols": [ - 4, - 5, - 6, - 8 - ] - }, - "ext": { - "bidder": { - "tagid": "123456" - } - } - } - ], - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 - }, - "site": { - "domain": "www.publisher.com", - "page": "http://www.publisher.com/awesome/site" - }, - "user": { - "buyeruid": "test_reader_id" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Missing required video parameter", - "comparison": "literal" - } - ], - "httpCalls": [] -} diff --git a/adapters/sovrnXsp/params_test.go b/adapters/sovrnXsp/params_test.go new file mode 100644 index 00000000000..cca8b22cf51 --- /dev/null +++ b/adapters/sovrnXsp/params_test.go @@ -0,0 +1,56 @@ +package sovrnXsp + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + + if err != nil { + t.Fatalf("Failed to fetch json-schemas. %v", err) + } + + for _, param := range validParams { + if err := validator.Validate(openrtb_ext.BidderSovrnXsp, json.RawMessage(param)); err != nil { + t.Errorf("Schema rejected sovrnXsp params: %s", param) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + + if err != nil { + t.Fatalf("Failed to fetch json-schemas. %v", err) + } + + for _, param := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSovrnXsp, json.RawMessage(param)); err == nil { + t.Errorf("Schema allowed sovrnXsp params: %s", param) + } + } +} + +var validParams = []string{ + `{"pub_id":"1234"}`, + `{"pub_id":"1234","med_id":"1234"}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz"}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz","force_bid":true}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz","force_bid":false}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `0`, + `[]`, + `{}`, + `{"pub_id":""}`, + `{"pub_id":"123"}`, + `{"pub_id":"1234","zone_id":"123"}`, +} diff --git a/adapters/sovrnXsp/sovrnXsp.go b/adapters/sovrnXsp/sovrnXsp.go new file mode 100644 index 00000000000..a026f888c72 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsp.go @@ -0,0 +1,172 @@ +package sovrnXsp + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + + "github.com/prebid/openrtb/v19/openrtb2" +) + +type adapter struct { + Endpoint string +} + +// bidExt.CreativeType values. +const ( + creativeTypeBanner int = 0 + creativeTypeVideo = 1 + creativeTypeNative = 2 + creativeTypeAudio = 3 +) + +// Bid response extension from XSP. +type bidExt struct { + CreativeType int `json:"creative_type"` +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + appCopy := *request.App + if appCopy.Publisher == nil { + appCopy.Publisher = &openrtb2.Publisher{} + } else { + publisherCopy := *appCopy.Publisher + appCopy.Publisher = &publisherCopy + } + request.App = &appCopy + + var errors []error + var imps []openrtb2.Imp + + for idx, imp := range request.Imp { + if imp.Banner == nil && imp.Video == nil && imp.Native == nil { + continue + } + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%d: ext.bidder not provided", idx), + } + errors = append(errors, err) + continue + } + + var xspExt openrtb_ext.ExtImpSovrnXsp + if err := json.Unmarshal(bidderExt.Bidder, &xspExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), + } + errors = append(errors, err) + continue + } + + request.App.Publisher.ID = xspExt.PubID + if xspExt.MedID != "" { + request.App.ID = xspExt.MedID + } + if xspExt.ZoneID != "" { + imp.TagID = xspExt.ZoneID + } + imps = append(imps, imp) + } + + if len(imps) == 0 { + return nil, append(errors, &errortypes.BadInput{ + Message: "no matching impression with ad format", + }) + } + + request.Imp = imps + requestJson, err := json.Marshal(request) + if err != nil { + return nil, append(errors, err) + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.Endpoint, + Body: requestJson, + Headers: headers, + }}, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + var errors []error + result := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + var ext bidExt + if err := json.Unmarshal(bid.Ext, &ext); err != nil { + errors = append(errors, err) + continue + } + + var bidType openrtb_ext.BidType + var mkupType openrtb2.MarkupType + switch ext.CreativeType { + case creativeTypeBanner: + bidType = openrtb_ext.BidTypeBanner + mkupType = openrtb2.MarkupBanner + case creativeTypeVideo: + bidType = openrtb_ext.BidTypeVideo + mkupType = openrtb2.MarkupVideo + case creativeTypeNative: + bidType = openrtb_ext.BidTypeNative + mkupType = openrtb2.MarkupNative + default: + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported creative type: %d", ext.CreativeType), + }) + continue + } + + if bid.MType == 0 { + bid.MType = mkupType + } + + result.Bids = append(result.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + + if len(result.Bids) == 0 { + // it's possible an empty seat array was sent as a response + return nil, errors + } + return result, errors +} + +// Builder builds a new instance of the SovrnXSP adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + Endpoint: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/sovrnXsp/sovrnXsp_test.go b/adapters/sovrnXsp/sovrnXsp_test.go new file mode 100644 index 00000000000..4c93fc39fa8 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsp_test.go @@ -0,0 +1,20 @@ +package sovrnXsp + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSovrnXsp, config.Adapter{ + Endpoint: "http://xsp.lijit.com/json/rtb/prebid/server"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "sovrnXsptest", bidder) +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json new file mode 100644 index 00000000000..08f36adb91b --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 0 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 1, + "ext": { + "creative_type": 0 + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json new file mode 100644 index 00000000000..f82297e3bb2 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "native": { + "ver": "1.2", + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}" + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "native": { + "ver": "1.2", + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}" + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"ver\":\"1.2\",\"link\":{\"url\":\"https://sovrn.com\"},\"assets\":[{\"id\":0,\"img\":{\"w\":300,\"h\":250,\"url\":\"https://ads.smrtb.com/demo/ads/300x250.png\"}},{\"id\":1,\"title\":{\"text\":\"Test Ad\",\"len\":7}},{\"id\":2,\"data\":{\"value\":\"0\",\"len\":1}}]}", + "crid": "test_native_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 2 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "{\"ver\":\"1.2\",\"link\":{\"url\":\"https://sovrn.com\"},\"assets\":[{\"id\":0,\"img\":{\"w\":300,\"h\":250,\"url\":\"https://ads.smrtb.com/demo/ads/300x250.png\"}},{\"id\":1,\"title\":{\"text\":\"Test Ad\",\"len\":7}},{\"id\":2,\"data\":{\"value\":\"0\",\"len\":1}}]}", + "crid": "test_native_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 4, + "ext": { + "creative_type": 2 + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json new file mode 100644 index 00000000000..5ba8259f063 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "video": { + "w": 300, + "h": 250, + "protocols": [1,2,3,4,5,6,7,8], + "playbackmethod": [1], + "mimes": ["video/mp4"], + "skip": 1, + "api": [2], + "maxbitrate": 3000 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "bidfloor": 1.0, + "tagid": "FgUtQqop18uf1I2fwDie", + "video": { + "w": 300, + "h": 250, + "protocols": [1,2,3,4,5,6,7,8], + "playbackmethod": [1], + "mimes": ["video/mp4"], + "skip": 1, + "api": [2], + "maxbitrate": 3000 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "Mc0Sovrn PSA00:00:30", + "crid": "sovrn_psa_crid_1", + "cid": "sovrn_psa", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 1 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "Mc0Sovrn PSA00:00:30", + "crid": "sovrn_psa_crid_1", + "cid": "sovrn_psa", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 2, + "ext": { + "creative_type": 1 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json new file mode 100644 index 00000000000..d9719d0a2e1 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [{ + "value": "no matching impression with ad format", + "comparison": "literal" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json new file mode 100644 index 00000000000..0de8fa072f8 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [{}] + } + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json new file mode 100644 index 00000000000..10f647186fd --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 500", + "comparison": "regex" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json new file mode 100644 index 00000000000..73bc7d87a53 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 100 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [{ + "value": "Unsupported creative type: 100", + "comparison": "literal" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json new file mode 100644 index 00000000000..bb0e04a50fc --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/sspBC/sspbc.go b/adapters/sspBC/sspbc.go index 6b601c120e0..f1a91322999 100644 --- a/adapters/sspBC/sspbc.go +++ b/adapters/sspBC/sspbc.go @@ -11,10 +11,10 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/adapters/sspBC/sspbc_test.go b/adapters/sspBC/sspbc_test.go index 47a8ae5d183..3c1f931bfb3 100644 --- a/adapters/sspBC/sspbc_test.go +++ b/adapters/sspBC/sspbc_test.go @@ -3,9 +3,9 @@ package sspBC import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/stroeerCore/params_test.go b/adapters/stroeerCore/params_test.go index 92586189b6f..ac8075c2251 100644 --- a/adapters/stroeerCore/params_test.go +++ b/adapters/stroeerCore/params_test.go @@ -2,8 +2,9 @@ package stroeerCore import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/stroeerCore/stroeercore.go b/adapters/stroeerCore/stroeercore.go index 08ba83a9544..590e641ff1f 100644 --- a/adapters/stroeerCore/stroeercore.go +++ b/adapters/stroeerCore/stroeercore.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { @@ -30,6 +30,18 @@ type bidResponse struct { Height int64 `json:"height"` Ad string `json:"ad"` CrID string `json:"crid"` + Mtype string `json:"mtype"` +} + +func (b bidResponse) resolveMediaType() (mt openrtb2.MarkupType, bt openrtb_ext.BidType, err error) { + switch b.Mtype { + case "banner": + return openrtb2.MarkupBanner, openrtb_ext.BidTypeBanner, nil + case "video": + return openrtb2.MarkupVideo, openrtb_ext.BidTypeVideo, nil + default: + return mt, bt, fmt.Errorf("unable to determine media type for bid with id \"%s\"", b.BidID) + } } func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -51,6 +63,14 @@ func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapter bidderResponse.Currency = "EUR" for _, bid := range stroeerResponse.Bids { + markupType, bidType, err := bid.resolveMediaType() + if err != nil { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bid media type error: %s", err.Error()), + }) + continue + } + openRtbBid := openrtb2.Bid{ ID: bid.ID, ImpID: bid.BidID, @@ -59,11 +79,12 @@ func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapter Price: bid.CPM, AdM: bid.Ad, CrID: bid.CrID, + MType: markupType, } bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ Bid: &openRtbBid, - BidType: openrtb_ext.BidTypeBanner, + BidType: bidType, }) } diff --git a/adapters/stroeerCore/stroeercore_test.go b/adapters/stroeerCore/stroeercore_test.go index fc7a680add0..153f3137c07 100644 --- a/adapters/stroeerCore/stroeercore_test.go +++ b/adapters/stroeerCore/stroeercore_test.go @@ -3,9 +3,9 @@ package stroeerCore import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json index 0abdbc8b499..b91cc72fbde 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json @@ -161,7 +161,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "XYZijk" + "crid": "XYZijk", + "mtype": "banner" } ] } @@ -179,7 +180,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "XYZijk" + "crid": "XYZijk", + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json index 091f244c6ef..03d6f10214e 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json @@ -138,7 +138,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "qwert" + "crid": "qwert", + "mtype": "banner" }, { "id": "3929239282-02", @@ -147,7 +148,8 @@ "width": 770, "height": 250, "ad": "another advert", - "crid": "wasd" + "crid": "wasd", + "mtype": "banner" } ] } @@ -166,7 +168,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "qwert" + "crid": "qwert", + "mtype": 1 }, "type": "banner" }, @@ -178,7 +181,8 @@ "adm": "another advert", "w": 770, "h": 250, - "crid": "wasd" + "crid": "wasd", + "mtype": 1 }, "type": "banner" } diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json index ca6ea0c26ea..7dd860a72ba 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json @@ -105,7 +105,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "wasd" + "crid": "wasd", + "mtype": "banner" } ] } @@ -123,7 +124,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "wasd" + "crid": "wasd", + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json new file mode 100644 index 00000000000..57a6e39bc6e --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "banner ad", + "crid": "qwert", + "mtype": "banner" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "3929239282-01", + "impid": "3", + "price": 2, + "adm": "banner ad", + "w": 468, + "h": 60, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json new file mode 100644 index 00000000000..27cd32ba1d0 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json @@ -0,0 +1,186 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "tagid": "85310", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "banner ad", + "crid": "qwert", + "mtype": "banner" + }, + { + "id": "3929239282-02", + "bidId": "9", + "cpm": 7.21, + "width": 770, + "height": 250, + "ad": "video ad", + "crid": "wasd", + "mtype": "video" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "3929239282-01", + "impid": "3", + "price": 2, + "adm": "banner ad", + "w": 468, + "h": 60, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "3929239282-02", + "impid": "9", + "price": 7.21, + "adm": "video ad", + "w": 770, + "h": 250, + "crid": "wasd", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json new file mode 100644 index 00000000000..65b2da804ea --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "8923356982838-09", + "bidId": "3", + "cpm": 2, + "ad": "
video
", + "crid": "wasd", + "mtype": "video" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "8923356982838-09", + "impid": "3", + "price": 2, + "adm": "
video
", + "crid": "wasd", + "mtype": 2 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json b/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json new file mode 100644 index 00000000000..0cb74cd47a6 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json @@ -0,0 +1,193 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "banner-1", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-2", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-3", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "id", + "imp": [ + { + "id": "banner-1", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-2", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-3", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "banner-1", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 1", + "crid": "qwert", + "mtype": "unknown" + }, + { + "id": "3929239282-02", + "bidId": "banner-2", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 2", + "crid": "qwert" + }, + { + "id": "3929239282-03", + "bidId": "banner-3", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 3", + "crid": "qwert", + "mtype": "banner" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "3929239282-03", + "impid": "banner-3", + "price": 2, + "adm": "banner ad 3", + "w": 300, + "h": 200, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + }] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid media type error: unable to determine media type for bid with id \"banner-1\"", + "comparison": "literal" + }, + { + "value": "Bid media type error: unable to determine media type for bid with id \"banner-2\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/suntContent/suntContent.go b/adapters/suntContent/suntContent.go deleted file mode 100644 index f5440737bd9..00000000000 --- a/adapters/suntContent/suntContent.go +++ /dev/null @@ -1,145 +0,0 @@ -package suntContent - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type adapter struct { - endpoint string -} - -func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &adapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - for i := range request.Imp { - if err := addTagID(&request.Imp[i]); err != nil { - return nil, []error{err} - } - } - - if !curExists(request.Cur, "EUR") { - request.Cur = append(request.Cur, "EUR") - } - - requestJSON, err := json.Marshal(request) - if err != nil { - return nil, []error{err} - } - - requestData := &adapters.RequestData{ - Method: http.MethodPost, - Uri: a.endpoint, - Body: requestJSON, - } - - return []*adapters.RequestData{requestData}, nil -} - -func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if responseData.StatusCode == http.StatusNoContent { - return nil, nil - } - - if responseData.StatusCode == http.StatusBadRequest { - err := &errortypes.BadInput{ - Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", - } - return nil, []error{err} - } - - if responseData.StatusCode != http.StatusOK { - err := &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), - } - return nil, []error{err} - } - - var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) - bidResponse.Currency = response.Cur - - var errs []error - - for _, seatBid := range response.SeatBid { - for i := range seatBid.Bid { - resolvePriceMacro(&seatBid.Bid[i]) - - bidType, err := getMediaTypeForBid(seatBid.Bid[i].Ext) - if err != nil { - errs = append(errs, err) - continue - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &seatBid.Bid[i], - BidType: bidType, - }) - } - } - - return bidResponse, errs -} - -func resolvePriceMacro(bid *openrtb2.Bid) { - price := strconv.FormatFloat(bid.Price, 'f', -1, 64) - bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1) -} - -func getMediaTypeForBid(ext json.RawMessage) (openrtb_ext.BidType, error) { - var bidExt openrtb_ext.ExtBid - - if err := json.Unmarshal(ext, &bidExt); err != nil { - return "", fmt.Errorf("could not unmarshal openrtb_ext.ExtBid: %w", err) - } - - if bidExt.Prebid == nil { - return "", fmt.Errorf("bid.Ext.Prebid is empty") - } - - return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) -} - -func curExists(allowedCurrencies []string, newCurrency string) bool { - for i := range allowedCurrencies { - if allowedCurrencies[i] == newCurrency { - return true - } - } - return false -} - -func addTagID(imp *openrtb2.Imp) error { - var ext adapters.ExtImpBidder - var extSA openrtb_ext.ImpExtSuntContent - - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err) - } - - if err := json.Unmarshal(ext.Bidder, &extSA); err != nil { - return fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSuntContent: %w", err) - } - - imp.TagID = extSA.AdUnitID - - return nil -} diff --git a/adapters/suntContent/suntContent_test.go b/adapters/suntContent/suntContent_test.go deleted file mode 100644 index 52be77efb60..00000000000 --- a/adapters/suntContent/suntContent_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package suntContent - -import ( - "encoding/json" - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderSuntContent, config.Adapter{ - Endpoint: "https://mockup.suntcontent.com/", - }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "suntContenttest", bidder) -} - -func TestResolvePriceMacro(t *testing.T) { - adm := `{"link":{"url":"https://some_url.com/abc123?wp=${AUCTION_PRICE}"}` - want := `{"link":{"url":"https://some_url.com/abc123?wp=12.34"}` - - bid := openrtb2.Bid{AdM: adm, Price: 12.34} - resolvePriceMacro(&bid) - - if bid.AdM != want { - t.Fatalf("want: %v, got: %v", want, bid.AdM) - } -} - -func TestGetMediaTypeForBid(t *testing.T) { - tests := []struct { - name string - want openrtb_ext.BidType - invalidJSON bool - wantErr bool - wantErrContain string - bidType openrtb_ext.BidType - }{ - { - name: "native", - want: openrtb_ext.BidTypeNative, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "native", - }, - { - name: "banner", - want: openrtb_ext.BidTypeBanner, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "banner", - }, - { - name: "video", - want: openrtb_ext.BidTypeVideo, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "video", - }, - { - name: "audio", - want: openrtb_ext.BidTypeAudio, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "audio", - }, - { - name: "empty type", - want: "", - invalidJSON: false, - wantErr: true, - wantErrContain: "invalid BidType", - bidType: "", - }, - { - name: "invalid type", - want: "", - invalidJSON: false, - wantErr: true, - wantErrContain: "invalid BidType", - bidType: "invalid", - }, - { - name: "invalid json", - want: "", - invalidJSON: true, - wantErr: true, - wantErrContain: "bid.Ext.Prebid is empty", - bidType: "", - }, - } - - for _, test := range tests { - var bid openrtb2.SeatBid - var extBid openrtb_ext.ExtBid - - var bidExtJsonString string - if test.invalidJSON { - bidExtJsonString = `{"x_prebid": {"type":""}}` - } else { - bidExtJsonString = `{"prebid": {"type":"` + string(test.bidType) + `"}}` - } - - if err := bid.Ext.UnmarshalJSON([]byte(bidExtJsonString)); err != nil { - t.Fatalf("unexpected error %v", err) - } - - if err := json.Unmarshal(bid.Ext, &extBid); err != nil { - t.Fatalf("could not unmarshal extBid: %v", err) - } - - got, gotErr := getMediaTypeForBid(bid.Ext) - assert.Equal(t, test.want, got) - - if test.wantErr { - if gotErr != nil { - assert.Contains(t, gotErr.Error(), test.wantErrContain) - continue - } - t.Fatalf("wantErr: %v, gotErr: %v", test.wantErr, gotErr) - } - } -} - -func TestAddTagID(t *testing.T) { - tests := []struct { - name string - want string - data string - wantErr bool - }{ - {"regular case", "abc123", "abc123", false}, - {"nil case", "", "", false}, - {"unmarshal err case", "", "", true}, - } - - for _, test := range tests { - extSA, err := json.Marshal(openrtb_ext.ImpExtSuntContent{AdUnitID: test.data}) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - extBidder, err := json.Marshal(adapters.ExtImpBidder{Bidder: extSA}) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - if test.wantErr { - extBidder = []byte{} - } - - ortbImp := openrtb2.Imp{Ext: extBidder} - - if err := addTagID(&ortbImp); err != nil { - if test.wantErr { - continue - } - t.Fatalf("unexpected error %v", err) - } - - if test.want != ortbImp.TagID { - t.Fatalf("want: %v, got: %v", test.want, ortbImp.TagID) - } - } -} - -func TestCurExists(t *testing.T) { - tests := []struct { - name string - cur string - data []string - want bool - }{ - {"no eur", "EUR", []string{"USD"}, false}, - {"eur exists", "EUR", []string{"USD", "EUR"}, true}, - } - - for _, test := range tests { - got := curExists(test.data, test.cur) - assert.Equal(t, test.want, got) - } -} diff --git a/adapters/suntContent/suntContenttest/exemplary/native.json b/adapters/suntContent/suntContenttest/exemplary/native.json deleted file mode 100644 index 89ef4e07dfb..00000000000 --- a/adapters/suntContent/suntContenttest/exemplary/native.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - }, - "cur": [ - "EUR" - ], - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "tagid": "example-tag-id", - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ], - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "some-id", - "impid": "test-imp-id", - "price": 1, - "adid": "69595837", - "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"],\"jstracker\":\"\"}", - "ext": { - "prebid": { - "type": "native" - } - } - } - ], - "seat": "123" - } - ], - "bidid": "8141327771600527856", - "cur": "EUR" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "EUR", - "bids": [ - { - "bid": { - "id": "some-id", - "impid": "test-imp-id", - "price": 1, - "adid": "69595837", - "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=1\"],\"jstracker\":\"\"}", - "ext": { - "prebid": { - "type": "native" - } - } - }, - "type": "native" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json b/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json deleted file mode 100644 index 1493d336f9d..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "could not unmarshal openrtb_ext.ImpExtSuntContent: json: cannot unmarshal number into Go struct field ImpExtSuntContent.adUnitID of type string", - "comparison": "literal" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - }, - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": 1234 - } - } - } - ] - } -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json b/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json deleted file mode 100644 index d7e62bb90cb..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json b/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json deleted file mode 100644 index 07618aed7fa..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 404 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go index 51a9833cdcb..adcaa0334f7 100644 --- a/adapters/taboola/params_test.go +++ b/adapters/taboola/params_test.go @@ -2,8 +2,9 @@ package taboola import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go index cb47fc9b250..8f4e6220143 100644 --- a/adapters/taboola/taboola.go +++ b/adapters/taboola/taboola.go @@ -11,11 +11,11 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { @@ -253,8 +253,7 @@ func makeRequestExt(pageType string) (json.RawMessage, error) { requestExtJson, err := json.Marshal(requestExt) if err != nil { - fmt.Errorf("could not marshal %s", requestExt) - return nil, err + return nil, fmt.Errorf("could not marshal %s, err: %s", requestExt, err) } return requestExtJson, nil diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go index 320d08da22f..bd674440150 100644 --- a/adapters/taboola/taboola_test.go +++ b/adapters/taboola/taboola_test.go @@ -1,14 +1,12 @@ package taboola import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/stretchr/testify/assert" "testing" -) -import ( - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index 8a248345994..ddfcbeb021f 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -2,8 +2,9 @@ package tappx import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index d66a10a2bbe..d05a59316ae 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -11,11 +11,11 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const TAPPX_BIDDER_VERSION = "1.5" diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index c1b711426fb..b3c6f3fe625 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -4,9 +4,9 @@ import ( "regexp" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/teads/models.go b/adapters/teads/models.go new file mode 100644 index 00000000000..5b63c163197 --- /dev/null +++ b/adapters/teads/models.go @@ -0,0 +1,40 @@ +package teads + +import ( + "encoding/json" + "text/template" +) + +type adapter struct { + endpointTemplate *template.Template +} + +type defaultBidderImpExtension struct { + Bidder bidder `json:"bidder"` +} + +type bidder struct { + PlacementId int `json:"placementId"` +} + +type teadsImpExtension struct { + KV teadsKV `json:"kv"` +} + +type teadsKV struct { + PlacementId int `json:"placementId"` +} + +type teadsBidExt struct { + Prebid teadsPrebidExt `json:"prebid"` +} + +type teadsPrebidExt struct { + Meta teadsPrebidMeta `json:"meta"` +} + +type teadsPrebidMeta struct { + RendererName string `json:"rendererName"` + RendererVersion string `json:"rendererVersion"` + RendererData json.RawMessage `json:"rendererData"` +} diff --git a/adapters/teads/teads.go b/adapters/teads/teads.go new file mode 100644 index 00000000000..72ee97f5a6e --- /dev/null +++ b/adapters/teads/teads.go @@ -0,0 +1,202 @@ +package teads + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "text/template" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// Builder builds a new instance of the Teads adapter for the given bidder with the given config. +func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpointTemplate: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the bid request", + }} + } + + endpointURL, err := a.buildEndpointURL() + if endpointURL == "" { + return nil, []error{err} + } + + if err := updateImpObject(request.Imp); err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: "Error parsing BidRequest object", + }} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + return []*adapters.RequestData{{ + Method: "POST", + Uri: endpointURL, + Body: reqJSON, + Headers: headers, + }}, []error{} +} + +func updateImpObject(imps []openrtb2.Imp) error { + for i := range imps { + imp := &imps[i] + + if imp.Banner != nil { + if len(imp.Banner.Format) != 0 { + bannerCopy := *imp.Banner + bannerCopy.H = &imp.Banner.Format[0].H + bannerCopy.W = &imp.Banner.Format[0].W + imp.Banner = &bannerCopy + } + } + + var defaultImpExt defaultBidderImpExtension + if err := json.Unmarshal(imp.Ext, &defaultImpExt); err != nil { + return &errortypes.BadInput{ + Message: "Error parsing Imp.Ext object", + } + } + if defaultImpExt.Bidder.PlacementId == 0 { + return &errortypes.BadInput{ + Message: "placementId should not be 0.", + } + } + imp.TagID = strconv.Itoa(defaultImpExt.Bidder.PlacementId) + teadsImpExt := &teadsImpExtension{ + KV: teadsKV{ + PlacementId: defaultImpExt.Bidder.PlacementId, + }, + } + if extJson, err := json.Marshal(teadsImpExt); err != nil { + return &errortypes.BadInput{ + Message: "Error stringify Imp.Ext object", + } + } else { + imp.Ext = extJson + } + } + return nil +} + +// Builds enpoint url based on adapter-specific pub settings from imp.ext +func (a *adapter) buildEndpointURL() (string, error) { + endpointParams := macros.EndpointTemplateParams{} + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) + + if err != nil { + return "", &errortypes.BadInput{ + Message: "Unable to parse endpoint url template: " + err.Error(), + } + } + + thisURI, err := url.Parse(host) + if err != nil { + return "", &errortypes.BadInput{ + Message: "Malformed URL: " + err.Error(), + } + } + + return thisURI.String(), nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + err := adapters.CheckResponseStatusCodeForErrors(response) + if err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid)) + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + + bidExtTeads, err := getTeadsRendererFromBidExt(bid.Ext) + if err != nil { + return nil, err + } + bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) + if err != nil { + return nil, err + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidMeta: &openrtb_ext.ExtBidPrebidMeta{ + RendererName: bidExtTeads.Prebid.Meta.RendererName, + RendererVersion: bidExtTeads.Prebid.Meta.RendererVersion, + }, + BidType: bidType, + }) + } + } + if bidResp.Cur != "" { + bidderResponse.Currency = bidResp.Cur + } + return bidderResponse, nil +} + +func getTeadsRendererFromBidExt(ext json.RawMessage) (*teadsBidExt, []error) { + var bidExtTeads teadsBidExt + if err := json.Unmarshal(ext, &bidExtTeads); err != nil { + return nil, []error{err} + } + if bidExtTeads.Prebid.Meta.RendererName == "" { + return nil, []error{&errortypes.BadInput{ + Message: "RendererName should not be empty if present", + }} + } + if bidExtTeads.Prebid.Meta.RendererVersion == "" { + return nil, []error{&errortypes.BadInput{ + Message: "RendererVersion should not be empty if present", + }} + } + return &bidExtTeads, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, []error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + return openrtb_ext.BidTypeBanner, nil + } + } + return openrtb_ext.BidType(""), []error{&errortypes.BadInput{ + Message: "Imp ids were not equals", + }} +} diff --git a/adapters/teads/teads_test.go b/adapters/teads/teads_test.go new file mode 100644 index 00000000000..c9f807ace21 --- /dev/null +++ b/adapters/teads/teads_test.go @@ -0,0 +1,28 @@ +package teads + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ + Endpoint: "https://psrv.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "teadstest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json new file mode 100644 index 00000000000..ffaf849bfd8 --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/exemplary/simple-banner.json b/adapters/teads/teadstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..43a28614f9c --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-banner.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/exemplary/simple-video.json b/adapters/teads/teadstest/exemplary/simple-video.json new file mode 100644 index 00000000000..de273aba904 --- /dev/null +++ b/adapters/teads/teadstest/exemplary/simple-video.json @@ -0,0 +1,184 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json new file mode 100644 index 00000000000..b2e5c9e7aba --- /dev/null +++ b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "cur": "EUR", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "does-not-match", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Imp ids were not equals", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/currency-empty-string.json b/adapters/teads/teadstest/supplemental/currency-empty-string.json new file mode 100644 index 00000000000..9168f265cae --- /dev/null +++ b/adapters/teads/teadstest/supplemental/currency-empty-string.json @@ -0,0 +1,185 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "cur": "EUR", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/no-impression-response.json b/adapters/teads/teadstest/supplemental/no-impression-response.json new file mode 100644 index 00000000000..de273aba904 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/no-impression-response.json @@ -0,0 +1,184 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/kubient/kubienttest/supplemental/no-imps.json b/adapters/teads/teadstest/supplemental/no-impression.json similarity index 76% rename from adapters/kubient/kubienttest/supplemental/no-imps.json rename to adapters/teads/teadstest/supplemental/no-impression.json index 189adf9a932..7b1cdceb9e1 100644 --- a/adapters/kubient/kubienttest/supplemental/no-imps.json +++ b/adapters/teads/teadstest/supplemental/no-impression.json @@ -1,8 +1,8 @@ { "mockBidRequest": { - "id": "test-no-imp-request-id", - "imp": [] + "id": "test-request-id" }, + "expectedMakeRequestsErrors": [ { "value": "No impression in the bid request", diff --git a/adapters/kubient/kubienttest/supplemental/missing-zoneid.json b/adapters/teads/teadstest/supplemental/no-placementId.json similarity index 70% rename from adapters/kubient/kubienttest/supplemental/missing-zoneid.json rename to adapters/teads/teadstest/supplemental/no-placementId.json index cfd616621e2..e343bc82a34 100644 --- a/adapters/kubient/kubienttest/supplemental/missing-zoneid.json +++ b/adapters/teads/teadstest/supplemental/no-placementId.json @@ -3,16 +3,12 @@ "id": "test-request-id", "imp": [ { - "id": "test-missing-req-param-id", + "id": "test-imp-id-1", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, @@ -22,10 +18,11 @@ } ] }, + "expectedMakeRequestsErrors": [ { - "value": "zoneid is empty", + "value": "placementId should not be 0.", "comparison": "literal" } ] -} +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/renderer-name-empty.json b/adapters/teads/teadstest/supplemental/renderer-name-empty.json new file mode 100644 index 00000000000..9d35cf2fe71 --- /dev/null +++ b/adapters/teads/teadstest/supplemental/renderer-name-empty.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "", + "version": "5.0.25", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "", + "rendererVersion": "5.0.25", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "RendererName should not be empty if present", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/teads/teadstest/supplemental/renderer-version-empty.json b/adapters/teads/teadstest/supplemental/renderer-version-empty.json new file mode 100644 index 00000000000..cb1a47d6fbe --- /dev/null +++ b/adapters/teads/teadstest/supplemental/renderer-version-empty.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://psrv.teads.tv/prebid-server/bid-request", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "tagid": "125", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 15, + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 940, + "h": 560 + }, + "ext": { + "kv": { + "placementId": 125 + } + } + } + ], + "ext": { + "prebid": { + "sdk": { + "renderers": [ + { + "name": "teads", + "version": "", + "data": { + "resize": true, + "sdkEngineVersion": "189" + } + } + ] + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "39312703-e970-4914-ae56-8e7d7d1fd16b", + "tagid": "125", + "seatbid": [ + { + "seat": "teads", + "bid": [ + { + "id": "695ac187-fb3f-4d1f-8d5d-099c5e4c4d28", + "impid": "b6321d41-3840-4cb3-baad-b6fc5b0c8553", + "price": 33, + "nurl": "https://localhost:8080/prebid-server/win-notice?data=base64&clearingPrice=${AUCTION_PRICE}", + "adm": "{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"ads\":[{\"settings\":{\"values\":{\"animations\":{\"expand\":0,\"collapse\":0.5},\"placementId\":2,\"adType\":\"video\",\"placementFormat\":\"inread\",\"allowedPlayer\":\"any\",\"pageId\":2},\"components\":{\"closeButton\":{\"display\":false,\"countdown\":0},\"credits\":{\"display\":false},\"soundButton\":{\"display\":true,\"countdown\":0,\"type\":\"equalizer\"},\"label\":{\"display\":false},\"slider\":{\"closeButtonDisplay\":false}},\"behaviors\":{\"smartPosition\":{\"top\":false,\"corner\":false,\"mustBypassWhitelist\":true},\"slider\":{\"enable\":false},\"friendly\":false,\"playerClick\":\"fullscreen\",\"soundStart\":{\"type\":\"mute\"},\"soundMute\":\"threshold\",\"soundOver\":\"over\",\"launch\":\"auto\",\"videoStart\":\"threshold\",\"videoPause\":\"threshold\",\"secure\":false}},\"type\":\"VastXml\",\"content\":\"Teads Technology\",\"scenario_id\":971105412,\"dsp_campaign_id\":\"1\",\"dsp_creative_id\":\"1\",\"insertion_id\":1,\"placement_id\":2,\"portfolio_item_id\":971104812}],\"wigoEnabled\":false,\"placementMetadata\":{\"2\":{\"adCallTrackingUrl\":\"https://localhost:18281/track?action=adCall&pid=2&pageId=2&auctid=39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6&vid=708ca808-ec55-4d97-ab81-9c4777e16058&hb_provider=prebid-server&hb_ad_unit_code=742d38c4-7994-4c2b-ac82-18d3a64ba3c7&env=thirdparty-inapp>c=1&gdpr_apply=false&gac=1&gap=1&ca=false&bsg=uncat&bsias=uncat&pfid=971104812&gid=1&brid=0&cid=1&rpm_reason=3&ut=1&p=5fwoPMJCquIB-txdmwQS0l79-hhHVnlTzyR9mmnBMtZRceP6-q31KzCfLpS8WTNaw_sXr-hkOFBxaxa-jyLblbVc&cts=1685971107773&cs=267268361555465193905\",\"auctionId\":\"39312703-e970-4914-ae56-8e7d7d1fd16b__b6321d41-3840-4cb3-baad-b6fc5b0c8553__c0f2e6ba-63d0-4e20-ab41-fe0822eb65a6\"}},\"viewerId\":\"708ca808-ec55-4d97-ab81-9c4777e16058\"}", + "adid": "1", + "adomain": [ + "teads.com" + ], + "cid": "1", + "crid": "1", + "cat": [ + "IAB1-6", + "IAB10-5" + ], + "ext": { + "prebid": { + "meta": { + "rendererName": "teads", + "rendererVersion": "", + "rendererData": { + "resize": true, + "sdkEngineVersion": "189" + } + } + } + } + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "RendererVersion should not be empty if present", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/kubient/kubienttest/supplemental/status_400.json b/adapters/teads/teadstest/supplemental/status-400.json similarity index 80% rename from adapters/kubient/kubienttest/supplemental/status_400.json rename to adapters/teads/teadstest/supplemental/status-400.json index 29438cc3b8b..98b1875402c 100644 --- a/adapters/kubient/kubienttest/supplemental/status_400.json +++ b/adapters/teads/teadstest/supplemental/status-400.json @@ -14,7 +14,7 @@ }, "ext": { "bidder": { - "zoneid": "102" + "placementId": 1 } } } @@ -24,23 +24,26 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://127.0.0.1:5000/bid", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ { "id": "test-imp-id", + "tagid": "1", "banner": { "format": [ { "w": 300, "h": 250 } - ] + ], + "w": 300, + "h": 250 }, "ext": { - "bidder": { - "zoneid": "102" + "kv": { + "placementId": 1 } } } @@ -53,11 +56,10 @@ } } ], - "expectedMakeBidsErrors": [ { "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", "comparison": "literal" } ] -} +} \ No newline at end of file diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json b/adapters/teads/teadstest/supplemental/status-500.json similarity index 71% rename from adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json rename to adapters/teads/teadstest/supplemental/status-500.json index f02bd478656..4db6eed0ec8 100644 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_400.json +++ b/adapters/teads/teadstest/supplemental/status-500.json @@ -14,7 +14,7 @@ }, "ext": { "bidder": { - "pid": "123" + "placementId": 1 } } } @@ -24,23 +24,26 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ { "id": "test-imp-id", + "tagid": "1", "banner": { "format": [ { "w": 300, "h": 250 } - ] + ], + "w": 300, + "h": 250 }, "ext": { - "bidder": { - "pid": "123" + "kv": { + "placementId": 1 } } } @@ -48,16 +51,15 @@ } }, "mockResponse": { - "status": 400, + "status": 500, "body": {} } } ], - "expectedMakeBidsErrors": [ { - "value": "Invalid request.", + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", "comparison": "literal" } ] -} +} \ No newline at end of file diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go index efa3fba1be9..9e451ca091e 100644 --- a/adapters/telaria/params_test.go +++ b/adapters/telaria/params_test.go @@ -2,8 +2,9 @@ package telaria import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index bbe600178f4..a1ac5611e1f 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -7,10 +7,10 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go index f8008835ac3..3c7d1bea46e 100644 --- a/adapters/telaria/telaria_test.go +++ b/adapters/telaria/telaria_test.go @@ -3,9 +3,9 @@ package telaria import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/tpmn/params_test.go b/adapters/tpmn/params_test.go new file mode 100644 index 00000000000..4715d910855 --- /dev/null +++ b/adapters/tpmn/params_test.go @@ -0,0 +1,45 @@ +package tpmn + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +var validParams = []string{ + `{"inventoryId": 10000001}`, +} + +var invalidParams = []string{ + `{"inventoryId": "00000001"}`, + `{"inventoryid": 100000000}`, +} + +// TestValidParams makes sure that the tpmn schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTpmn, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected TPMN params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the tpmn schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTpmn, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/tpmn/tpmn.go b/adapters/tpmn/tpmn.go new file mode 100644 index 00000000000..443b8837bac --- /dev/null +++ b/adapters/tpmn/tpmn.go @@ -0,0 +1,126 @@ +package tpmn + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +// TpmnAdapter struct +type adapter struct { + uri string +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids from TpmnBidder. +func (rcv *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + validImps, errs := getValidImpressions(request, reqInfo) + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + requestBodyJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Uri: rcv.uri, + Body: requestBodyJSON, + Headers: headers, + }}, errs +} + +// getValidImpressions validate imps and check for bid floor currency. Convert to EUR if necessary +func getValidImpressions(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]openrtb2.Imp, []error) { + var errs []error + var validImps []openrtb2.Imp + + for _, imp := range request.Imp { + if err := preprocessBidFloorCurrency(&imp, reqInfo); err != nil { + errs = append(errs, err) + continue + } + validImps = append(validImps, imp) + } + return validImps, errs +} + +func preprocessBidFloorCurrency(imp *openrtb2.Imp, reqInfo *adapters.ExtraRequestInfo) error { + // we expect every currency related data to be EUR + if imp.BidFloor > 0 && strings.ToUpper(imp.BidFloorCur) != "USD" && imp.BidFloorCur != "" { + if convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD"); err != nil { + return err + } else { + imp.BidFloor = convertedValue + } + } + imp.BidFloorCur = "USD" + return nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{fmt.Errorf("bid response unmarshal: %v", err)} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForImp(bid) + if err != nil { + return nil, []error{err} + } + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unsupported MType %d", bid.MType) + } +} + +// Builder builds a new instance of the TpmnBidder adapter for the given bidder with the given config. +func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + uri: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/tpmn/tpmn_test.go b/adapters/tpmn/tpmn_test.go new file mode 100644 index 00000000000..7170dbb3d5f --- /dev/null +++ b/adapters/tpmn/tpmn_test.go @@ -0,0 +1,20 @@ +package tpmn + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTpmn, config.Adapter{ + Endpoint: "https://gat.tpmn.io/ortb/pbs_bidder"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "tpmntest", bidder) +} diff --git a/adapters/tpmn/tpmntest/exemplary/simple-banner.json b/adapters/tpmn/tpmntest/exemplary/simple-banner.json new file mode 100644 index 00000000000..197d03b174e --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-banner.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "00000001", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "00000001", + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "tpmn" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/tpmn/tpmntest/exemplary/simple-native.json b/adapters/tpmn/tpmntest/exemplary/simple-native.json new file mode 100644 index 00000000000..1880c74ac7e --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-native.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "tpmn" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "mtype": 4, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json b/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json new file mode 100644 index 00000000000..8f7c5d59301 --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "tpmn" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} + \ No newline at end of file diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-native.json b/adapters/tpmn/tpmntest/exemplary/simple-site-native.json new file mode 100644 index 00000000000..20e6c23e966 --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-native.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "device": { + "ip":"123.123.123.123" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "device": { + "ip":"123.123.123.123" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":60,\"hmin\":60,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":75}},{\"id\":4,\"required\":0,\"data\":{\"type\":6,\"len\":1000}},{\"id\":5,\"required\":0,\"data\":{\"type\":7,\"len\":1000}},{\"id\":6,\"required\":0,\"data\":{\"type\":11,\"len\":1000}}]}" + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "tpmn", + "bid": [{ + "id": "1", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-adm", + "crid": "test-crid", + "mtype": 4 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-adm", + "crid": "test-crid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-video.json b/adapters/tpmn/tpmntest/exemplary/simple-site-video.json new file mode 100644 index 00000000000..7b2375cd07a --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-video.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "device": { + "ip":"123.123.123.123" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "device": { + "ip":"123.123.123.123" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 3, + 5, + 6 + ], + "w": 1024, + "h": 576 + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "tpmn", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-adm", + "crid": "test-crid", + "w": 1024, + "h": 576, + "mtype": 2 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-adm", + "crid": "test-crid", + "w": 1024, + "h": 576, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/tpmn/tpmntest/exemplary/simple-video.json b/adapters/tpmn/tpmntest/exemplary/simple-video.json new file mode 100644 index 00000000000..505b6167069 --- /dev/null +++ b/adapters/tpmn/tpmntest/exemplary/simple-video.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "tpmn" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "mtype": 2, + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json b/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json new file mode 100644 index 00000000000..3bbb23f95a4 --- /dev/null +++ b/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "00000001", + "ext": { + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "00000001", + "bidfloorcur": "USD", + "ext": { + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + "expectedBidResponses": [ + ] +} diff --git a/adapters/tpmn/tpmntest/supplemental/bad_response.json b/adapters/tpmn/tpmntest/supplemental/bad_response.json new file mode 100644 index 00000000000..12c3a72d49f --- /dev/null +++ b/adapters/tpmn/tpmntest/supplemental/bad_response.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid response unmarshal: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json b/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json new file mode 100644 index 00000000000..8bcbb93d004 --- /dev/null +++ b/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "16", + "ext": "", + "bidfloorcur": "USD" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "bid response unmarshal: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json b/adapters/tpmn/tpmntest/supplemental/status-204.json similarity index 54% rename from adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json rename to adapters/tpmn/tpmntest/supplemental/status-204.json index ed4d8ff38b8..fdcd3f7fd55 100644 --- a/adapters/nanointeractive/nanointeractivetest/supplemental/status_204.json +++ b/adapters/tpmn/tpmntest/supplemental/status-204.json @@ -9,22 +9,33 @@ { "w": 300, "h": 250 + }, + { + "w": 300, + "h": 600 } ] }, "ext": { "bidder": { - "pid": "123" + "inventoryId": 10000001 } } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } }, - "httpCalls": [ { "expectedRequest": { - "uri": "https://ad.audiencemanager.de/hbs", + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", "body": { "id": "test-request-id", "imp": [ @@ -35,16 +46,29 @@ { "w": 300, "h": 250 + }, + { + "w": 300, + "h": 600 } ] }, + "bidfloorcur": "USD", "ext": { "bidder": { - "pid": "123" + "inventoryId": 10000001 } } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } } }, "mockResponse": { @@ -53,6 +77,5 @@ } } ], - "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/tpmn/tpmntest/supplemental/status-404.json b/adapters/tpmn/tpmntest/supplemental/status-404.json new file mode 100644 index 00000000000..74ced15217c --- /dev/null +++ b/adapters/tpmn/tpmntest/supplemental/status-404.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://gat.tpmn.io/ortb/pbs_bidder", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "inventoryId": 10000001 + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/trafficgate/params_test.go b/adapters/trafficgate/params_test.go index 4dc2c792bc9..adc11c08335 100644 --- a/adapters/trafficgate/params_test.go +++ b/adapters/trafficgate/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TestValidParams makes sure that the trafficgate schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/trafficgate/trafficgate.go b/adapters/trafficgate/trafficgate.go index 3c9ebe9ba98..1462fe59bd5 100644 --- a/adapters/trafficgate/trafficgate.go +++ b/adapters/trafficgate/trafficgate.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/trafficgate/trafficgate_test.go b/adapters/trafficgate/trafficgate_test.go index 326c50523fe..473c9d5d5c3 100644 --- a/adapters/trafficgate/trafficgate_test.go +++ b/adapters/trafficgate/trafficgate_test.go @@ -3,9 +3,9 @@ package trafficgate import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 0e7fbe4a462..793647bccaa 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TripleliftAdapter struct { diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index add71b05788..c4468a93faa 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -3,9 +3,9 @@ package triplelift import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 9131c79a975..08aefb3b135 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type TripleliftNativeAdapter struct { diff --git a/adapters/triplelift_native/triplelift_native_test.go b/adapters/triplelift_native/triplelift_native_test.go index 18e157a41cd..c1c82501b32 100644 --- a/adapters/triplelift_native/triplelift_native_test.go +++ b/adapters/triplelift_native/triplelift_native_test.go @@ -3,9 +3,9 @@ package triplelift_native import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go index b721925e72a..9bba397a084 100644 --- a/adapters/ucfunnel/params_test.go +++ b/adapters/ucfunnel/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/ucfunnel.json diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index a0d86a0fa29..cb4a6093c99 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -7,10 +7,10 @@ import ( "net/url" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type UcfunnelAdapter struct { diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index a906b9279e8..6e5000c3205 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestMakeRequests(t *testing.T) { diff --git a/adapters/undertone/params_test.go b/adapters/undertone/params_test.go index b48d08188d5..3144f757078 100644 --- a/adapters/undertone/params_test.go +++ b/adapters/undertone/params_test.go @@ -2,8 +2,9 @@ package undertone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/undertone/undertone.go b/adapters/undertone/undertone.go index 62934fa5954..a5f428b12ca 100644 --- a/adapters/undertone/undertone.go +++ b/adapters/undertone/undertone.go @@ -8,10 +8,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const adapterId = 4 @@ -26,6 +26,11 @@ type undertoneParams struct { Version string `json:"version"` } +type impExt struct { + Bidder *openrtb_ext.ExtImpUndertone `json:"bidder,omitempty"` + Gpid string `json:"gpid,omitempty"` +} + func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ endpoint: config.Endpoint, @@ -158,24 +163,29 @@ func getImpsAndPublisherId(bidRequest *openrtb2.BidRequest) ([]openrtb2.Imp, int var validImps []openrtb2.Imp for _, imp := range bidRequest.Imp { - var extImpBidder adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &extImpBidder); err != nil { - errs = append(errs, getInvalidImpErr(imp.ID, err)) - continue - } - - var extImpUndertone openrtb_ext.ExtImpUndertone - if err := json.Unmarshal(extImpBidder.Bidder, &extImpUndertone); err != nil { + var ext impExt + if err := json.Unmarshal(imp.Ext, &ext); err != nil { errs = append(errs, getInvalidImpErr(imp.ID, err)) continue } if publisherId == 0 { - publisherId = extImpUndertone.PublisherID + publisherId = ext.Bidder.PublisherID } - imp.TagID = strconv.Itoa(extImpUndertone.PlacementID) + imp.TagID = strconv.Itoa(ext.Bidder.PlacementID) imp.Ext = nil + + if ext.Gpid != "" { + ext.Bidder = nil + impExtJson, err := json.Marshal(&ext) + if err != nil { + errs = append(errs, getInvalidImpErr(imp.ID, err)) + continue + } + imp.Ext = impExtJson + } + validImps = append(validImps, imp) } diff --git a/adapters/undertone/undertone_test.go b/adapters/undertone/undertone_test.go index d7e6d52339b..c08460e8627 100644 --- a/adapters/undertone/undertone_test.go +++ b/adapters/undertone/undertone_test.go @@ -1,10 +1,11 @@ package undertone import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json index e6b4a83ae3a..71ad2ae199d 100644 --- a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json +++ b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json @@ -54,7 +54,8 @@ "bidder": { "publisherId": 1234, "placementId": 123456 - } + }, + "gpid": "gpid-value" } } ], @@ -116,7 +117,10 @@ 4 ] }, - "tagid": "123456" + "tagid": "123456", + "ext": { + "gpid": "gpid-value" + } } ], "app": { diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json index 0de38caa986..9e59ffbca63 100644 --- a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json +++ b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json @@ -20,7 +20,8 @@ "bidder": { "publisherId": 1234, "placementId": 12345 - } + }, + "gpid": "gpid-value" } }, { @@ -87,7 +88,10 @@ } ] }, - "tagid": "12345" + "tagid": "12345", + "ext": { + "gpid": "gpid-value" + } }, { "id": "test-imp-video-id", diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go index 9313183fbfa..fd76995d1c0 100644 --- a/adapters/unicorn/params_test.go +++ b/adapters/unicorn/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index 5048d9d2394..6351ad059cf 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -8,10 +8,10 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go index 084be78498a..6c1e5aa73f2 100644 --- a/adapters/unicorn/unicorn_test.go +++ b/adapters/unicorn/unicorn_test.go @@ -3,9 +3,9 @@ package unicorn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go index e9607358a59..f8feea872c8 100644 --- a/adapters/unruly/params_test.go +++ b/adapters/unruly/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 1f4bf6b0203..b96da9eb93e 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index b5d837abea5..8407ba15212 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -3,9 +3,9 @@ package unruly import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go index b638d4585c6..dbc815fd76d 100644 --- a/adapters/videobyte/params_test.go +++ b/adapters/videobyte/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/videobyte.json diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go index 2dc6df84895..afbce1376f9 100644 --- a/adapters/videobyte/videobyte.go +++ b/adapters/videobyte/videobyte.go @@ -6,10 +6,10 @@ import ( "net/http" "net/url" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go index d4dda0606f8..9e566a20ef2 100644 --- a/adapters/videobyte/videobyte_test.go +++ b/adapters/videobyte/videobyte_test.go @@ -3,9 +3,9 @@ package videobyte import ( "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/videoheroes/params_test.go b/adapters/videoheroes/params_test.go index d79f83245a4..66e1e6a2788 100644 --- a/adapters/videoheroes/params_test.go +++ b/adapters/videoheroes/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/videoheroes/videoheroes.go b/adapters/videoheroes/videoheroes.go index d4efcab2c90..0014c1613e4 100755 --- a/adapters/videoheroes/videoheroes.go +++ b/adapters/videoheroes/videoheroes.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/videoheroes/videoheroes_test.go b/adapters/videoheroes/videoheroes_test.go index ac60d56e175..7c0b2268f51 100644 --- a/adapters/videoheroes/videoheroes_test.go +++ b/adapters/videoheroes/videoheroes_test.go @@ -3,9 +3,9 @@ package videoheroes import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/vidoomy/params_test.go b/adapters/vidoomy/params_test.go index 63ffb462c19..40c17029f9e 100644 --- a/adapters/vidoomy/params_test.go +++ b/adapters/vidoomy/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/vidoomy.json diff --git a/adapters/vidoomy/vidoomy.go b/adapters/vidoomy/vidoomy.go index 7e7e9d64eb3..09e924e596c 100644 --- a/adapters/vidoomy/vidoomy.go +++ b/adapters/vidoomy/vidoomy.go @@ -7,10 +7,10 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/vidoomy/vidoomy_test.go b/adapters/vidoomy/vidoomy_test.go index 60cd2c9d967..7acc477ae1c 100644 --- a/adapters/vidoomy/vidoomy_test.go +++ b/adapters/vidoomy/vidoomy_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestVidoomyBidderEndpointConfig(t *testing.T) { diff --git a/adapters/visiblemeasures/params_test.go b/adapters/visiblemeasures/params_test.go index 7bcc1cf60cf..ed74ef1ad35 100644 --- a/adapters/visiblemeasures/params_test.go +++ b/adapters/visiblemeasures/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visiblemeasures/visiblemeasures.go b/adapters/visiblemeasures/visiblemeasures.go index 3d6a96640e9..7b8cb9a9dd3 100644 --- a/adapters/visiblemeasures/visiblemeasures.go +++ b/adapters/visiblemeasures/visiblemeasures.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/visiblemeasures/visiblemeasures_test.go b/adapters/visiblemeasures/visiblemeasures_test.go index 8c1759c010e..8970ccb1e43 100644 --- a/adapters/visiblemeasures/visiblemeasures_test.go +++ b/adapters/visiblemeasures/visiblemeasures_test.go @@ -3,9 +3,9 @@ package visiblemeasures import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/visx/params_test.go b/adapters/visx/params_test.go index e53d2cda007..0646d221e27 100644 --- a/adapters/visx/params_test.go +++ b/adapters/visx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 713c2693990..a7cc232c01a 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type VisxAdapter struct { diff --git a/adapters/visx/visx_test.go b/adapters/visx/visx_test.go index 5fc58a1f83d..8cdccc2e653 100644 --- a/adapters/visx/visx_test.go +++ b/adapters/visx/visx_test.go @@ -3,9 +3,9 @@ package visx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vox/params_test.go b/adapters/vox/params_test.go new file mode 100644 index 00000000000..e23a57d9b30 --- /dev/null +++ b/adapters/vox/params_test.go @@ -0,0 +1,56 @@ +package vox + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderVox, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderVox, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "64be6fe6685a271d37e900d2"}`, + `{"placementId": "Any String Basically"}`, + `{"placementId":""}`, + `{"placementId":"id", "imageUrl":"http://site.com/img1.png"}`, + `{"placementId":"id", "imageUrl":"http://site.com/img1.png", "displaySizes":["123x90", "1x1", "987x1111"]}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{"placementId": null}`, + `{"placementId": 3.1415}`, + `{"placementId": true}`, + `{"placementId": false}`, + `{"placementId":"id", "imageUrl": null}`, + `{"placementId":"id", "imageUrl": true}`, + `{"placementId":"id", "imageUrl": []}`, + `{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": null}`, + `{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": {}}`, + `{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": "String"}`, +} diff --git a/adapters/vox/vox.go b/adapters/vox/vox.go new file mode 100644 index 00000000000..740fe84e611 --- /dev/null +++ b/adapters/vox/vox.go @@ -0,0 +1,87 @@ +package vox + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) + } +} diff --git a/adapters/vox/vox_test.go b/adapters/vox/vox_test.go new file mode 100644 index 00000000000..dfb345b2e02 --- /dev/null +++ b/adapters/vox/vox_test.go @@ -0,0 +1,21 @@ +package vox + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderVox, config.Adapter{ + Endpoint: "http://somecoolurlfor.vox"}, + config.Server{ExternalUrl: "http://somecoolurlfor.vox", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error: %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "voxtest", bidder) +} diff --git a/adapters/vox/voxtest/exemplary/banner.json b/adapters/vox/voxtest/exemplary/banner.json new file mode 100644 index 00000000000..898fcea4826 --- /dev/null +++ b/adapters/vox/voxtest/exemplary/banner.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6", + "site": { + "page": "http://some.site/url/page?id=338" + }, + "cur": [ "USD" ], + "imp": [{ + "id": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9", + "banner": { "w": 100, "h": 100 }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "placementId":"64b939146d66df22ccae95c5" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://somecoolurlfor.vox", + "headers": {}, + "body": { + "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6", + "site": { + "page": "http://some.site/url/page?id=338" + }, + "imp": [ + { + "id": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9", + "banner": { + "w": 100, + "h": 100 + }, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "placementId": "64b939146d66df22ccae95c5" + } + } + } + ], + "cur": [ "USD" ] + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6", + "cur": "USD", + "seatbid": [{ + "bid": [{ + "id": "e6a143f3-5176-4607-b09e-0e67e358b0b6", + "impid": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9", + "price": 3.1415, + "adm": "

Hi, there

", + "w": 50, + "h": 50, + "mtype": 1 + }] + }] + }} + }], + + "expectedBidResponses": [{ + "bids": [{ + "currency": "USD", + "bid": { + "id": "e6a143f3-5176-4607-b09e-0e67e358b0b6", + "impid": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9", + "price": 3.1415, + "adm": "

Hi, there

", + "w": 50, + "h": 50, + "mtype": 1 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/vox/voxtest/exemplary/video.json b/adapters/vox/voxtest/exemplary/video.json new file mode 100644 index 00000000000..37464da1aec --- /dev/null +++ b/adapters/vox/voxtest/exemplary/video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "9a4bdb67-43ef-488f-937d-cd01e9dce43d", + "site": { + "page": "http://blog.com/article/1" + }, + "cur": ["PLN"], + "imp": [{ + "id": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a", + "bidfloorcur": "PLN", + "video": { + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 15, + "w": 1280, + "h": 720 + }, + "ext": { + "bidder": { + "placementId":"64b9486efecad2330718e233" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://somecoolurlfor.vox", + "header": {}, + "body": { + "id": "9a4bdb67-43ef-488f-937d-cd01e9dce43d", + "site": { + "page": "http://blog.com/article/1" + }, + "cur": [ "PLN" ], + "imp": [{ + "id": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a", + "bidfloorcur": "PLN", + "video": { + "mimes": ["video/mp4"], + "minduration": 5, + "maxduration": 15, + "w": 1280, + "h": 720 + }, + "ext": { + "bidder": { + "placementId":"64b9486efecad2330718e233" + } + } + }] + } + }, + + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id":"d64fdeae-cb1c-4322-8fb6-18f5ec49bb76", + "cur": "PLN", + "seatbid": [{ + "bid": [{ + "id": "05349123-29e2-4be0-b662-48914f75ebe1", + "impid": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a", + "price": 2.149, + "adm": "...", + "mtype": 2 + }] + }] + } + } + }], + + "expectedBidResponses": [{ + "bids": [{ + "currency": "PLN", + "bid": { + "id": "05349123-29e2-4be0-b662-48914f75ebe1", + "impid": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a", + "price": 2.149, + "adm": "...", + "mtype": 2 + }, + "type": "video" + }] + }] +} \ No newline at end of file diff --git a/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json new file mode 100644 index 00000000000..0a49b85d612 --- /dev/null +++ b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "Some Id (445455)", + "cur": ["AUD"], + "imp": [{ + "id": "Impression id #1", + "banner": {}, + "bidfloorcur": "AUD", + "ext": { + "bidder": { + "placementId": "64b94ad4ed136806629dd51c" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://somecoolurlfor.vox", + "headers": {}, + "body": { + "id": "Some Id (445455)", + "cur": ["AUD"], + "imp": [{ + "id": "Impression id #1", + "banner": {}, + "bidfloorcur": "AUD", + "ext": { + "bidder": { + "placementId": "64b94ad4ed136806629dd51c" + } + } + }] + } + }, + + "mockResponse": { + "status": 204, + "headers": {}, + "body": {} + } + }], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/vox/voxtest/supplemental/response_500_to_error.json b/adapters/vox/voxtest/supplemental/response_500_to_error.json new file mode 100644 index 00000000000..f245ef45c99 --- /dev/null +++ b/adapters/vox/voxtest/supplemental/response_500_to_error.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "It is hard to make up ids.", + "site": { + "page": "http://catsanddogs.com/article/887" + }, + "cur": ["USD"], + "imp": [{ + "id": "Impression id #4", + "bidfloorcur": "USD", + "video": { "w": 1280, "h": 720, "mimes": ["video/mp4"] }, + "ext": { + "bidder": { + "placementId": "64b94ca2b5eb3605e0a0e3be" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://somecoolurlfor.vox", + "heades": {}, + "body": { + "id": "It is hard to make up ids.", + "site": { + "page": "http://catsanddogs.com/article/887" + }, + "cur": ["USD"], + "imp": [{ + "id": "Impression id #4", + "bidfloorcur": "USD", + "video": { "w": 1280, "h": 720, "mimes": ["video/mp4"] }, + "ext": { + "bidder": { + "placementId": "64b94ca2b5eb3605e0a0e3be" + } + } + }] + } + }, + + "mockResponse": { + "status": 500, + "headers": {}, + "body": {} + } + }], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 500", + "comparison": "regex" + }] +} \ No newline at end of file diff --git a/adapters/vrtcal/params_test.go b/adapters/vrtcal/params_test.go index d45d3b39013..0e30dd6fcc9 100644 --- a/adapters/vrtcal/params_test.go +++ b/adapters/vrtcal/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) //Vrtcal doesn't currently require any custom fields. This file is included for conformity only diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index 65e20b2116f..01ef178b352 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type VrtcalAdapter struct { @@ -71,7 +71,7 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR var errs []error for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bidType, err := getReturnTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidType, err := getReturnTypeForImp(sb.Bid[i].MType) if err == nil { bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], @@ -94,25 +94,15 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getReturnTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } - - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } - - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unsupported return type for ID: \"%s\"", impID), - } - } - } - - //Failsafe default in case impression ID is not found - return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), +func getReturnTypeForImp(mType openrtb2.MarkupType) (openrtb_ext.BidType, error) { + if mType == openrtb2.MarkupBanner { + return openrtb_ext.BidTypeBanner, nil + } else if mType == openrtb2.MarkupVideo { + return openrtb_ext.BidTypeVideo, nil + } else if mType == openrtb2.MarkupNative { + return openrtb_ext.BidTypeNative, nil + } else { + return "", &errortypes.BadServerResponse{ + Message: "Unsupported return type"} } } diff --git a/adapters/vrtcal/vrtcal_test.go b/adapters/vrtcal/vrtcal_test.go index 31e6c78e2c1..a4ba917922f 100644 --- a/adapters/vrtcal/vrtcal_test.go +++ b/adapters/vrtcal/vrtcal_test.go @@ -3,9 +3,9 @@ package vrtcal import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json index 81808a3b0f2..27682f7ee21 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json @@ -68,7 +68,8 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "mtype":1 } ] } @@ -90,7 +91,8 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "mtype":1 }, "type": "banner" } diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json new file mode 100644 index 00000000000..3c63914c3a6 --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + + "native": { + "ver": "1.2", + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.2", + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "vrtcal", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-native-ad", + "crid": "crid_10", + "mtype":4 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-native-ad", + "crid": "crid_10", + "mtype":4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json index 365c892b87f..2baaf1aa4ba 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json @@ -64,7 +64,8 @@ "adm": "some-test-video-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "mtype":2 } ] } @@ -86,7 +87,8 @@ "adm": "some-test-video-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "mtype":2 }, "type": "video" } diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json index 62fd90cfc61..eb7d2310d63 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json @@ -70,7 +70,8 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "mtype":1 } ] } @@ -92,7 +93,8 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "mtype":1 }, "type": "banner" } diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json new file mode 100644 index 00000000000..6b416ab0726 --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.2", + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "ver": "1.2", + "request": "{\"assets\":[{\"id\":1,\"img\":{\"ext\":{\"image1\":\"image2\"},\"h\": 250,\"mimes\":[\"image\/gif\",\"image\/png\"],\"type\":3,\"w\":300},\"required\":1}]}" + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "site": { + "id": "fake-site-id", + "page": "http://www.vrtcal.com" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "vrtcal", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-native-ad", + "crid": "crid_10", + "mtype":4 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-native-ad", + "crid": "crid_10", + "mtype":4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json index f70c547a040..96ffd7099d1 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json @@ -66,7 +66,8 @@ "adm": "some-test-video-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "mtype":2 } ] } @@ -88,7 +89,8 @@ "adm": "some-test-video-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "mtype":2 }, "type": "video" } diff --git a/adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json b/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json similarity index 90% rename from adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json rename to adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json index b3bfc0604ab..e2fc18f373e 100644 --- a/adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json +++ b/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json @@ -63,13 +63,14 @@ "bid": [ { "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "WRONG-IMPRESSION_ID", + "impid": "test-imp-id", "price": 0.500000, "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 - } + "w": 300, + "mtype": 0 + } ] } ], @@ -81,7 +82,7 @@ "expectedBidResponses": [{"currency":"USD","bids":[]}], "expectedMakeBidsErrors": [ { - "value": "Failed to find impression for ID: \"WRONG-IMPRESSION_ID\"", + "value": "Unsupported return type", "comparison": "literal" } ] diff --git a/adapters/xeworks/params_test.go b/adapters/xeworks/params_test.go index 68d36096049..1c14b3a0989 100644 --- a/adapters/xeworks/params_test.go +++ b/adapters/xeworks/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/xeworks/xeworks.go b/adapters/xeworks/xeworks.go index 35e551b1034..e892fcfc932 100644 --- a/adapters/xeworks/xeworks.go +++ b/adapters/xeworks/xeworks.go @@ -7,11 +7,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type bidType struct { diff --git a/adapters/xeworks/xeworks_test.go b/adapters/xeworks/xeworks_test.go index 4869a05a229..db7e26c9bae 100644 --- a/adapters/xeworks/xeworks_test.go +++ b/adapters/xeworks/xeworks_test.go @@ -3,9 +3,9 @@ package xeworks import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/yahooAdvertising/params_test.go b/adapters/yahooAds/params_test.go similarity index 60% rename from adapters/yahooAdvertising/params_test.go rename to adapters/yahooAds/params_test.go index 10d504cb291..dbbc2c84adb 100644 --- a/adapters/yahooAdvertising/params_test.go +++ b/adapters/yahooAds/params_test.go @@ -1,17 +1,17 @@ -package yahooAdvertising +package yahooAds import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -// This file actually intends to test static/bidder-params/yahooAdvertising.json +// This file actually intends to test static/bidder-params/yahooAds.json // -// These also validate the format of the external API: request.imp[i].ext.yahooAdvertising +// These also validate the format of the external API: request.imp[i].ext.yahooAds -// TestValidParams makes sure that the yahooAdvertising schema accepts all imp.ext fields which we intend to support. +// TestValidParams makes sure that the yahooAds schema accepts all imp.ext fields which we intend to support. func TestValidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -19,13 +19,13 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderYahooAdvertising, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected yahooAdvertising params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderYahooAds, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected yahooAds params: %s", validParam) } } } -// TestInvalidParams makes sure that the yahooAdvertising schema rejects all the imp.ext fields we don't support. +// TestInvalidParams makes sure that the yahooAds schema rejects all the imp.ext fields we don't support. func TestInvalidParams(t *testing.T) { validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { @@ -33,7 +33,7 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderYahooAdvertising, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderYahooAds, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } diff --git a/adapters/yahooAdvertising/yahooAdvertising.go b/adapters/yahooAds/yahooAds.go similarity index 90% rename from adapters/yahooAdvertising/yahooAdvertising.go rename to adapters/yahooAds/yahooAds.go index d476adb4e8e..241f4eba506 100644 --- a/adapters/yahooAdvertising/yahooAdvertising.go +++ b/adapters/yahooAds/yahooAds.go @@ -1,4 +1,4 @@ -package yahooAdvertising +package yahooAds import ( "encoding/json" @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { @@ -41,8 +41,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E continue } - var yahooAdvertisingExt openrtb_ext.ExtImpYahooAdvertising - err = json.Unmarshal(bidderExt.Bidder, &yahooAdvertisingExt) + var yahooAdsExt openrtb_ext.ExtImpYahooAds + err = json.Unmarshal(bidderExt.Bidder, &yahooAdsExt) if err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), @@ -64,7 +64,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E reqCopy.App = &appCopy } - if err := changeRequestForBidService(&reqCopy, &yahooAdvertisingExt); err != nil { + if err := changeRequestForBidService(&reqCopy, &yahooAdsExt); err != nil { errors = append(errors, err) continue } @@ -150,7 +150,7 @@ func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { return exists, mediaType } -func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYahooAdvertising) error { +func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYahooAds) error { /* Always override the tag ID and (site ID or app ID) of the request */ request.Imp[0].TagID = extension.Pos if request.Site != nil { @@ -218,7 +218,7 @@ func validateBanner(banner *openrtb2.Banner) error { return nil } -// Builder builds a new instance of the YahooAdvertising adapter for the given bidder with the given config. +// Builder builds a new instance of the YahooAds adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { bidder := &adapter{ URI: config.Endpoint, diff --git a/adapters/yahooAds/yahooAds_test.go b/adapters/yahooAds/yahooAds_test.go new file mode 100644 index 00000000000..ae9103d141e --- /dev/null +++ b/adapters/yahooAds/yahooAds_test.go @@ -0,0 +1,35 @@ +package yahooAds + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func TestYahooAdsBidderEndpointConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYahooAds, config.Adapter{ + Endpoint: "http://localhost/bid", + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + bidderYahooAds := bidder.(*adapter) + + assert.Equal(t, "http://localhost/bid", bidderYahooAds.URI) +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYahooAds, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "yahooAdstest", bidder) +} diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json rename to adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json index d5e77c71d3d..7ad41161915 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json rename to adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json index d08bdf06019..7036664d4ad 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json rename to adapters/yahooAds/yahooAdstest/exemplary/simple-video.json index ea95ec40306..ebf7af93d53 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json @@ -92,7 +92,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/empty-banner-format.json b/adapters/yahooAds/yahooAdstest/supplemental/empty-banner-format.json similarity index 100% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/empty-banner-format.json rename to adapters/yahooAds/yahooAdstest/supplemental/empty-banner-format.json diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-height.json b/adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-height.json similarity index 100% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-height.json rename to adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-height.json diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-width.json b/adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-width.json similarity index 100% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-width.json rename to adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-width.json diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json rename to adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json index a44020daf3f..c0d77fa496b 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json @@ -74,7 +74,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/required-nobidder-info.json b/adapters/yahooAds/yahooAdstest/supplemental/required-nobidder-info.json similarity index 100% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/required-nobidder-info.json rename to adapters/yahooAds/yahooAdstest/supplemental/required-nobidder-info.json diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-error.json b/adapters/yahooAds/yahooAdstest/supplemental/server-error.json similarity index 100% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-error.json rename to adapters/yahooAds/yahooAdstest/supplemental/server-error.json diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json rename to adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json index 1fcf61e12b7..f40819497a8 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json @@ -76,7 +76,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "wrong", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json index 190f96bf0a6..94c895b996d 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json @@ -92,7 +92,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json index 81c89f55c3d..3d5aff6c531 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json @@ -87,7 +87,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json similarity index 98% rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json index 353d5e29e71..1206005970c 100644 --- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json @@ -78,7 +78,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yahooAdvertising", + "seat": "yahooAds", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yahooAdvertising/yahooAdvertising_test.go b/adapters/yahooAdvertising/yahooAdvertising_test.go deleted file mode 100644 index e701c3335c1..00000000000 --- a/adapters/yahooAdvertising/yahooAdvertising_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package yahooAdvertising - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestYahooAdvertisingBidderEndpointConfig(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYahooAdvertising, config.Adapter{ - Endpoint: "http://localhost/bid", - }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - bidderYahooAdvertising := bidder.(*adapter) - - assert.Equal(t, "http://localhost/bid", bidderYahooAdvertising.URI) -} - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYahooAdvertising, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "yahooAdvertisingtest", bidder) -} diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go index 997bf93a53f..805be75da30 100644 --- a/adapters/yeahmobi/params_test.go +++ b/adapters/yeahmobi/params_test.go @@ -2,8 +2,9 @@ package yeahmobi import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index 3079225876d..35434d05473 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -7,16 +7,15 @@ import ( "net/url" "text/template" - "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -type YeahmobiAdapter struct { +type adapter struct { EndpointTemplate *template.Template } @@ -27,16 +26,16 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) } - bidder := &YeahmobiAdapter{ + bidder := &adapter{ EndpointTemplate: template, } return bidder, nil } -func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData - adapterRequest, errs := adapter.makeRequest(request) + adapterRequest, errs := a.makeRequest(request) if errs == nil { adapterRequests = append(adapterRequests, adapterRequest) } @@ -44,16 +43,15 @@ func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqIn return adapterRequests, errs } -func (adapter *YeahmobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error yeahmobiExt, errs := getYeahmobiExt(request) if yeahmobiExt == nil { - glog.Fatal("Invalid ExtImpYeahmobi value") return nil, errs } - endPoint, err := adapter.getEndpoint(yeahmobiExt) + endPoint, err := a.getEndpoint(yeahmobiExt) if err != nil { return nil, append(errs, err) } @@ -126,12 +124,12 @@ func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, } -func (adapter *YeahmobiAdapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (string, error) { - return macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{Host: "gw-" + url.QueryEscape(ext.ZoneId) + "-bid.yeahtargeter.com"}) +func (a *adapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (string, error) { + return macros.ResolveMacros(a.EndpointTemplate, macros.EndpointTemplateParams{Host: "gw-" + url.QueryEscape(ext.ZoneId) + "-bid.yeahtargeter.com"}) } // MakeBids make the bids for the bid response. -func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go index 0b1c39ef214..c1c7be35105 100644 --- a/adapters/yeahmobi/yeahmobi_test.go +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -3,9 +3,9 @@ package yeahmobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go index ed0d2863629..74a91ac3bf7 100644 --- a/adapters/yieldlab/params_test.go +++ b/adapters/yieldlab/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldlab.json diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 74ee6bd7220..c6aa7f27dac 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -12,10 +12,10 @@ import ( "golang.org/x/text/currency" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // YieldlabAdapter connects the Yieldlab API to prebid server diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go index d3fc9f3eb1d..4c4f8f56330 100644 --- a/adapters/yieldlab/yieldlab_test.go +++ b/adapters/yieldlab/yieldlab_test.go @@ -8,9 +8,9 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const testURL = "https://ad.yieldlab.net/testing/" diff --git a/adapters/yieldmo/params_test.go b/adapters/yieldmo/params_test.go index d94c7ff035b..647c21abb90 100644 --- a/adapters/yieldmo/params_test.go +++ b/adapters/yieldmo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldmo.json diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index bf8410b294b..69841e88a22 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type YieldmoAdapter struct { @@ -30,6 +30,10 @@ type Ext struct { Gpid string `json:"gpid,omitempty"` } +type ExtBid struct { + MediaType string `json:"mediatype,omitempty"` +} + func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -138,14 +142,18 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i]) + if err != nil { + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), + BidType: bidType, }) } } return bidResponse, nil - } // Builder builds a new instance of the Yieldmo adapter for the given bidder with the given config. @@ -156,12 +164,21 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - //default to video unless banner exists in impression - for _, imp := range imps { - if imp.ID == impId && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } +// Retrieve the media type corresponding to the bid from the bid.ext object +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + var bidExt ExtBid + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return "", &errortypes.BadInput{Message: err.Error()} + } + + switch bidExt.MediaType { + case "banner": + return openrtb_ext.BidTypeBanner, nil + case "video": + return openrtb_ext.BidTypeVideo, nil + case "native": + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("invalid BidType: %s", bidExt.MediaType) } - return openrtb_ext.BidTypeVideo } diff --git a/adapters/yieldmo/yieldmo_test.go b/adapters/yieldmo/yieldmo_test.go index 1d9426d0643..f89d4849a2c 100644 --- a/adapters/yieldmo/yieldmo_test.go +++ b/adapters/yieldmo/yieldmo_test.go @@ -3,9 +3,9 @@ package yieldmo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/yieldmo/yieldmotest/exemplary/app-banner.json b/adapters/yieldmo/yieldmotest/exemplary/app-banner.json index e674cf11539..92609cae16a 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/app-banner.json +++ b/adapters/yieldmo/yieldmotest/exemplary/app-banner.json @@ -65,7 +65,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -87,7 +91,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/app_video.json b/adapters/yieldmo/yieldmotest/exemplary/app_video.json index e33c37f69bf..6f94a30b0d8 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/app_video.json +++ b/adapters/yieldmo/yieldmotest/exemplary/app_video.json @@ -63,7 +63,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "video" + } } ] } @@ -85,7 +89,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "video" + } }, "type": "video" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json b/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json index 11739ca1d32..ba2435001c7 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json +++ b/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json @@ -65,7 +65,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -87,7 +91,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/simple_video.json b/adapters/yieldmo/yieldmotest/exemplary/simple_video.json index aaf53124365..ec65bc98b6c 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/simple_video.json +++ b/adapters/yieldmo/yieldmotest/exemplary/simple_video.json @@ -63,7 +63,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "video" + } } ] } @@ -85,7 +89,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "video" + } }, "type": "video" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json index bd9e911058a..d155f5db67f 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json +++ b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json @@ -73,7 +73,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -95,7 +99,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go index 6048ea5d7dc..623928839ef 100644 --- a/adapters/yieldone/params_test.go +++ b/adapters/yieldone/params_test.go @@ -2,8 +2,9 @@ package yieldone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 2d5f1d81173..e852c5cb6ba 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type YieldoneAdapter struct { diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go index 12d634d463d..1847ef9bf06 100644 --- a/adapters/yieldone/yieldone_test.go +++ b/adapters/yieldone/yieldone_test.go @@ -3,9 +3,9 @@ package yieldone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index 235f678d7bb..6f477352652 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -8,11 +8,11 @@ import ( "text/template" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type ZeroClickFraudAdapter struct { diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go index e07c43ff7a2..6e2a7e23b4c 100644 --- a/adapters/zeroclickfraud/zeroclickfraud_test.go +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -3,9 +3,9 @@ package zeroclickfraud import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json index cdfaf37e45d..8e04138937c 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json index 68aabbed257..3e1ee805552 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json index 248f4b0487a..bc1e496cb27 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "headers": { "Content-Type": [ "application/json;charset=utf-8" diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json index 38f3bd326d0..bf2af3bf25b 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json index 4a55670720d..0aba5f2ca36 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json index 1992586435f..1f5a92c4fdf 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json index 037c7307889..27e71c31255 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ diff --git a/adapters/zeta_global_ssp/zeta_global_ssp.go b/adapters/zeta_global_ssp/zeta_global_ssp.go index 7a5f3395724..dcfffc8b342 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp.go @@ -6,10 +6,10 @@ import ( "net/http" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type adapter struct { diff --git a/adapters/zeta_global_ssp/zeta_global_ssp_test.go b/adapters/zeta_global_ssp/zeta_global_ssp_test.go index 3b7be288fa1..fabfa5efed8 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp_test.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp_test.go @@ -3,14 +3,14 @@ package zeta_global_ssp import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters/adapterstest" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderZetaGlobalSsp, config.Adapter{ - Endpoint: "http://whatever.url"}, + Endpoint: "https://ssp.disqus.com/bid/prebid-server?sid=11"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { diff --git a/adservertargeting/adservertargeting.go b/adservertargeting/adservertargeting.go index 94d64579e66..e79b521e1f5 100644 --- a/adservertargeting/adservertargeting.go +++ b/adservertargeting/adservertargeting.go @@ -7,7 +7,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type DataSource string diff --git a/adservertargeting/adservertargeting_test.go b/adservertargeting/adservertargeting_test.go index 4a651fdd2be..d0a90ce0813 100644 --- a/adservertargeting/adservertargeting_test.go +++ b/adservertargeting/adservertargeting_test.go @@ -7,7 +7,8 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -42,7 +43,7 @@ func TestExtractAdServerTargeting(t *testing.T) { p := "https://www.test-url.com?ampkey=testAmpKey&data-override-height=400" u, _ := url.Parse(p) params := u.Query() - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") res, warnings := collect(rw, reqBytes, params) @@ -248,7 +249,7 @@ func TestProcessAdServerTargetingFull(t *testing.T) { bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)} - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") targetingKeyLen := 0 resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, &targetingKeyLen) @@ -331,7 +332,7 @@ func TestProcessAdServerTargetingWarnings(t *testing.T) { bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)} - reqBytes, err := json.Marshal(r) + reqBytes, err := jsonutil.Marshal(r) assert.NoError(t, err, "unexpected req marshal error") resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, nil) assert.Len(t, resResp.SeatBid, 2, "Incorrect response: seat bid number") diff --git a/adservertargeting/reqcache.go b/adservertargeting/reqcache.go index cb2edac9e4a..f2a4636c2f8 100644 --- a/adservertargeting/reqcache.go +++ b/adservertargeting/reqcache.go @@ -5,6 +5,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type requestCache struct { @@ -24,7 +25,7 @@ func (reqImpCache *requestCache) GetImpsData() ([]json.RawMessage, error) { } var impsData []json.RawMessage - err = json.Unmarshal(imps, &impsData) + err = jsonutil.Unmarshal(imps, &impsData) if err != nil { return nil, err } @@ -48,7 +49,7 @@ func (bidsCache *bidsCache) GetBid(bidderName, bidId string, bid openrtb2.Bid) ( } _, bidExists := bidsCache.bids[bidderName][bidId] if !bidExists { - bidBytes, err := json.Marshal(bid) + bidBytes, err := jsonutil.Marshal(bid) if err != nil { return nil, err } diff --git a/adservertargeting/requestlookup.go b/adservertargeting/requestlookup.go index debf19830db..d3284525b50 100644 --- a/adservertargeting/requestlookup.go +++ b/adservertargeting/requestlookup.go @@ -3,11 +3,12 @@ package adservertargeting import ( "encoding/json" "fmt" - "github.com/buger/jsonparser" - "github.com/pkg/errors" - "github.com/prebid/prebid-server/openrtb_ext" "net/url" "strings" + + "github.com/buger/jsonparser" + "github.com/pkg/errors" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func getAdServerTargeting(reqWrapper *openrtb_ext.RequestWrapper) ([]openrtb_ext.AdServerTarget, error) { diff --git a/adservertargeting/requestlookup_test.go b/adservertargeting/requestlookup_test.go index cd86364558e..1b54beb03e7 100644 --- a/adservertargeting/requestlookup_test.go +++ b/adservertargeting/requestlookup_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/respdataprocessor.go b/adservertargeting/respdataprocessor.go index 94a391d08bb..ab3ca070f89 100644 --- a/adservertargeting/respdataprocessor.go +++ b/adservertargeting/respdataprocessor.go @@ -7,7 +7,8 @@ import ( "github.com/pkg/errors" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -79,7 +80,7 @@ func buildBidExt(targetingData map[string]string, Targeting: targetingDataTruncated, }, } - bidExtTargeting, err := json.Marshal(bidExtTargetingData) + bidExtTargeting, err := jsonutil.Marshal(bidExtTargetingData) if err != nil { warnings = append(warnings, createWarning(err.Error())) return nil diff --git a/adservertargeting/respdataprocessor_test.go b/adservertargeting/respdataprocessor_test.go index 1118458e0f6..95404cef733 100644 --- a/adservertargeting/respdataprocessor_test.go +++ b/adservertargeting/respdataprocessor_test.go @@ -8,7 +8,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/utils.go b/adservertargeting/utils.go index 8093a4b6974..136f7900e24 100644 --- a/adservertargeting/utils.go +++ b/adservertargeting/utils.go @@ -1,11 +1,12 @@ package adservertargeting import ( + "strings" + "github.com/buger/jsonparser" "github.com/pkg/errors" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "strings" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func splitAndGet(path string, data []byte, delimiter string) (string, error) { diff --git a/amp/parse.go b/amp/parse.go index 34f1a3cacb4..a42ef6fa399 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -10,10 +10,10 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/gdpr" ) // Params defines the parameters of an AMP request. diff --git a/amp/parse_test.go b/amp/parse_test.go index 9f981fd30e0..38c7a05a615 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/analytics/build/build.go b/analytics/build/build.go new file mode 100644 index 00000000000..7fc577daedf --- /dev/null +++ b/analytics/build/build.go @@ -0,0 +1,93 @@ +package build + +import ( + "github.com/benbjohnson/clock" + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/clients" + "github.com/prebid/prebid-server/v2/analytics/filesystem" + "github.com/prebid/prebid-server/v2/analytics/pubstack" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/privacy" +) + +// Modules that need to be logged to need to be initialized here +func New(analytics *config.Analytics) analytics.Runner { + modules := make(enabledAnalytics, 0) + if len(analytics.File.Filename) > 0 { + if mod, err := filesystem.NewFileLogger(analytics.File.Filename); err == nil { + modules["filelogger"] = mod + } else { + glog.Fatalf("Could not initialize FileLogger for file %v :%v", analytics.File.Filename, err) + } + } + + if analytics.Pubstack.Enabled { + pubstackModule, err := pubstack.NewModule( + clients.GetDefaultHttpInstance(), + analytics.Pubstack.ScopeId, + analytics.Pubstack.IntakeUrl, + analytics.Pubstack.ConfRefresh, + analytics.Pubstack.Buffers.EventCount, + analytics.Pubstack.Buffers.BufferSize, + analytics.Pubstack.Buffers.Timeout, + clock.New()) + if err == nil { + modules["pubstack"] = pubstackModule + } else { + glog.Errorf("Could not initialize PubstackModule: %v", err) + } + } + return modules +} + +// Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface +type enabledAnalytics map[string]analytics.Module + +func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogAuctionObject(ao) + } + } +} + +func (ea enabledAnalytics) LogVideoObject(vo *analytics.VideoObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogVideoObject(vo) + } + } +} + +func (ea enabledAnalytics) LogCookieSyncObject(cso *analytics.CookieSyncObject) { + for _, module := range ea { + module.LogCookieSyncObject(cso) + } +} + +func (ea enabledAnalytics) LogSetUIDObject(so *analytics.SetUIDObject) { + for _, module := range ea { + module.LogSetUIDObject(so) + } +} + +func (ea enabledAnalytics) LogAmpObject(ao *analytics.AmpObject, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogAmpObject(ao) + } + } +} + +func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.NotificationEvent, ac privacy.ActivityControl) { + for name, module := range ea { + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} + if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + module.LogNotificationEventObject(ne) + } + } +} diff --git a/analytics/build/build_test.go b/analytics/build/build_test.go new file mode 100644 index 00000000000..efc0c862564 --- /dev/null +++ b/analytics/build/build_test.go @@ -0,0 +1,225 @@ +package build + +import ( + "net/http" + "os" + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +const TEST_DIR string = "testFiles" + +func TestSampleModule(t *testing.T) { + var count int + am := initAnalytics(&count) + am.LogAuctionObject(&analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + }, privacy.ActivityControl{}) + if count != 1 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogSetUIDObject(&analytics.SetUIDObject{ + Status: http.StatusOK, + Bidder: "bidders string", + UID: "uid", + Errors: nil, + Success: true, + }) + if count != 2 { + t.Errorf("PBSAnalyticsModule failed at LogSetUIDObject") + } + + am.LogCookieSyncObject(&analytics.CookieSyncObject{}) + if count != 3 { + t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject") + } + + am.LogAmpObject(&analytics.AmpObject{}, privacy.ActivityControl{}) + if count != 4 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{}, privacy.ActivityControl{}) + if count != 5 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, privacy.ActivityControl{}) + if count != 6 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +type sampleModule struct { + count *int +} + +func (m *sampleModule) LogAuctionObject(ao *analytics.AuctionObject) { *m.count++ } + +func (m *sampleModule) LogVideoObject(vo *analytics.VideoObject) { *m.count++ } + +func (m *sampleModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { *m.count++ } + +func (m *sampleModule) LogSetUIDObject(so *analytics.SetUIDObject) { *m.count++ } + +func (m *sampleModule) LogAmpObject(ao *analytics.AmpObject) { *m.count++ } + +func (m *sampleModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { *m.count++ } + +func initAnalytics(count *int) analytics.Runner { + modules := make(enabledAnalytics, 0) + modules["sampleModule"] = &sampleModule{count} + return &modules +} + +func TestNewPBSAnalytics(t *testing.T) { + pbsAnalytics := New(&config.Analytics{}) + instance := pbsAnalytics.(enabledAnalytics) + + assert.Equal(t, len(instance), 0) +} + +func TestNewPBSAnalytics_FileLogger(t *testing.T) { + if _, err := os.Stat(TEST_DIR); os.IsNotExist(err) { + if err = os.MkdirAll(TEST_DIR, 0755); err != nil { + t.Fatalf("Could not create test directory for FileLogger") + } + } + defer os.RemoveAll(TEST_DIR) + mod := New(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) + switch modType := mod.(type) { + case enabledAnalytics: + if len(enabledAnalytics(modType)) != 1 { + t.Fatalf("Failed to add analytics module") + } + default: + t.Fatalf("Failed to initialize analytics module") + } + + pbsAnalytics := New(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) + instance := pbsAnalytics.(enabledAnalytics) + + assert.Equal(t, len(instance), 1) +} + +func TestNewPBSAnalytics_Pubstack(t *testing.T) { + + pbsAnalyticsWithoutError := New(&config.Analytics{ + Pubstack: config.Pubstack{ + Enabled: true, + ScopeId: "scopeId", + IntakeUrl: "https://pubstack.io/intake", + Buffers: config.PubstackBuffer{ + BufferSize: "100KB", + EventCount: 0, + Timeout: "30s", + }, + ConfRefresh: "2h", + }, + }) + instanceWithoutError := pbsAnalyticsWithoutError.(enabledAnalytics) + + assert.Equal(t, len(instanceWithoutError), 1) + + pbsAnalyticsWithError := New(&config.Analytics{ + Pubstack: config.Pubstack{ + Enabled: true, + }, + }) + instanceWithError := pbsAnalyticsWithError.(enabledAnalytics) + assert.Equal(t, len(instanceWithError), 0) +} + +func TestSampleModuleActivitiesAllowed(t *testing.T) { + var count int + am := initAnalytics(&count) + + acAllowed := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", true)) + + ao := &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + } + + am.LogAuctionObject(ao, acAllowed) + if count != 1 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogAmpObject(&analytics.AmpObject{}, acAllowed) + if count != 2 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{}, acAllowed) + if count != 3 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, acAllowed) + if count != 4 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +func TestSampleModuleActivitiesDenied(t *testing.T) { + var count int + am := initAnalytics(&count) + + acDenied := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", false)) + + ao := &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + } + + am.LogAuctionObject(ao, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogAmpObject(&analytics.AmpObject{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, acDenied) + if count != 0 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + ReportAnalytics: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"analytics"}, + }, + }, + }, + }, + }, + } +} diff --git a/analytics/config/config.go b/analytics/config/config.go deleted file mode 100644 index 557fec361dc..00000000000 --- a/analytics/config/config.go +++ /dev/null @@ -1,80 +0,0 @@ -package config - -import ( - "github.com/benbjohnson/clock" - "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/clients" - "github.com/prebid/prebid-server/analytics/filesystem" - "github.com/prebid/prebid-server/analytics/pubstack" - "github.com/prebid/prebid-server/config" -) - -// Modules that need to be logged to need to be initialized here -func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule { - modules := make(enabledAnalytics, 0) - if len(analytics.File.Filename) > 0 { - if mod, err := filesystem.NewFileLogger(analytics.File.Filename); err == nil { - modules = append(modules, mod) - } else { - glog.Fatalf("Could not initialize FileLogger for file %v :%v", analytics.File.Filename, err) - } - } - - if analytics.Pubstack.Enabled { - pubstackModule, err := pubstack.NewModule( - clients.GetDefaultHttpInstance(), - analytics.Pubstack.ScopeId, - analytics.Pubstack.IntakeUrl, - analytics.Pubstack.ConfRefresh, - analytics.Pubstack.Buffers.EventCount, - analytics.Pubstack.Buffers.BufferSize, - analytics.Pubstack.Buffers.Timeout, - clock.New()) - if err == nil { - modules = append(modules, pubstackModule) - } else { - glog.Errorf("Could not initialize PubstackModule: %v", err) - } - } - return modules -} - -// Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface -type enabledAnalytics []analytics.PBSAnalyticsModule - -func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject) { - for _, module := range ea { - module.LogAuctionObject(ao) - } -} - -func (ea enabledAnalytics) LogVideoObject(vo *analytics.VideoObject) { - for _, module := range ea { - module.LogVideoObject(vo) - } -} - -func (ea enabledAnalytics) LogCookieSyncObject(cso *analytics.CookieSyncObject) { - for _, module := range ea { - module.LogCookieSyncObject(cso) - } -} - -func (ea enabledAnalytics) LogSetUIDObject(so *analytics.SetUIDObject) { - for _, module := range ea { - module.LogSetUIDObject(so) - } -} - -func (ea enabledAnalytics) LogAmpObject(ao *analytics.AmpObject) { - for _, module := range ea { - module.LogAmpObject(ao) - } -} - -func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.NotificationEvent) { - for _, module := range ea { - module.LogNotificationEventObject(ne) - } -} diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go deleted file mode 100644 index c0ad9c26a16..00000000000 --- a/analytics/config/config_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package config - -import ( - "net/http" - "os" - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" -) - -const TEST_DIR string = "testFiles" - -func TestSampleModule(t *testing.T) { - var count int - am := initAnalytics(&count) - am.LogAuctionObject(&analytics.AuctionObject{ - Status: http.StatusOK, - Errors: nil, - Response: &openrtb2.BidResponse{}, - }) - if count != 1 { - t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") - } - - am.LogSetUIDObject(&analytics.SetUIDObject{ - Status: http.StatusOK, - Bidder: "bidders string", - UID: "uid", - Errors: nil, - Success: true, - }) - if count != 2 { - t.Errorf("PBSAnalyticsModule failed at LogSetUIDObject") - } - - am.LogCookieSyncObject(&analytics.CookieSyncObject{}) - if count != 3 { - t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject") - } - - am.LogAmpObject(&analytics.AmpObject{}) - if count != 4 { - t.Errorf("PBSAnalyticsModule failed at LogAmpObject") - } - - am.LogVideoObject(&analytics.VideoObject{}) - if count != 5 { - t.Errorf("PBSAnalyticsModule failed at LogVideoObject") - } - - am.LogNotificationEventObject(&analytics.NotificationEvent{}) - if count != 6 { - t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") - } -} - -type sampleModule struct { - count *int -} - -func (m *sampleModule) LogAuctionObject(ao *analytics.AuctionObject) { *m.count++ } - -func (m *sampleModule) LogVideoObject(vo *analytics.VideoObject) { *m.count++ } - -func (m *sampleModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { *m.count++ } - -func (m *sampleModule) LogSetUIDObject(so *analytics.SetUIDObject) { *m.count++ } - -func (m *sampleModule) LogAmpObject(ao *analytics.AmpObject) { *m.count++ } - -func (m *sampleModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { *m.count++ } - -func initAnalytics(count *int) analytics.PBSAnalyticsModule { - modules := make(enabledAnalytics, 0) - modules = append(modules, &sampleModule{count}) - return &modules -} - -func TestNewPBSAnalytics(t *testing.T) { - pbsAnalytics := NewPBSAnalytics(&config.Analytics{}) - instance := pbsAnalytics.(enabledAnalytics) - - assert.Equal(t, len(instance), 0) -} - -func TestNewPBSAnalytics_FileLogger(t *testing.T) { - if _, err := os.Stat(TEST_DIR); os.IsNotExist(err) { - if err = os.MkdirAll(TEST_DIR, 0755); err != nil { - t.Fatalf("Could not create test directory for FileLogger") - } - } - defer os.RemoveAll(TEST_DIR) - mod := NewPBSAnalytics(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) - switch modType := mod.(type) { - case enabledAnalytics: - if len(enabledAnalytics(modType)) != 1 { - t.Fatalf("Failed to add analytics module") - } - default: - t.Fatalf("Failed to initialize analytics module") - } - - pbsAnalytics := NewPBSAnalytics(&config.Analytics{File: config.FileLogs{Filename: TEST_DIR + "/test"}}) - instance := pbsAnalytics.(enabledAnalytics) - - assert.Equal(t, len(instance), 1) -} - -func TestNewPBSAnalytics_Pubstack(t *testing.T) { - - pbsAnalyticsWithoutError := NewPBSAnalytics(&config.Analytics{ - Pubstack: config.Pubstack{ - Enabled: true, - ScopeId: "scopeId", - IntakeUrl: "https://pubstack.io/intake", - Buffers: config.PubstackBuffer{ - BufferSize: "100KB", - EventCount: 0, - Timeout: "30s", - }, - ConfRefresh: "2h", - }, - }) - instanceWithoutError := pbsAnalyticsWithoutError.(enabledAnalytics) - - assert.Equal(t, len(instanceWithoutError), 1) - - pbsAnalyticsWithError := NewPBSAnalytics(&config.Analytics{ - Pubstack: config.Pubstack{ - Enabled: true, - }, - }) - instanceWithError := pbsAnalyticsWithError.(enabledAnalytics) - assert.Equal(t, len(instanceWithError), 0) -} diff --git a/analytics/core.go b/analytics/core.go index eca93741bd2..122a3da8ad3 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -4,15 +4,15 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) -// PBSAnalyticsModule must be implemented by analytics modules to extract the required information and logging +// Module must be implemented by analytics modules to extract the required information and logging // activities. Do not use marshal the parameter objects directly as they can change over time. Use a separate // model for each analytics module and transform as appropriate. -type PBSAnalyticsModule interface { +type Module interface { LogAuctionObject(*AuctionObject) LogVideoObject(*VideoObject) LogCookieSyncObject(*CookieSyncObject) diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 9a357529c3a..4f7886c1206 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -2,12 +2,12 @@ package filesystem import ( "bytes" - "encoding/json" "fmt" "github.com/chasex/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type RequestType string @@ -86,7 +86,7 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) } // Method to initialize the analytic module -func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { +func NewFileLogger(filename string) (analytics.Module, error) { options := glog.LogOptions{ File: filename, Flag: glog.LstdFlags, @@ -120,7 +120,7 @@ func jsonifyAuctionObject(ao *analytics.AuctionObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logAuction }{ @@ -153,7 +153,7 @@ func jsonifyVideoObject(vo *analytics.VideoObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logVideo }{ @@ -178,7 +178,7 @@ func jsonifyCookieSync(cso *analytics.CookieSyncObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logUserSync }{ @@ -205,7 +205,7 @@ func jsonifySetUIDObject(so *analytics.SetUIDObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logSetUID }{ @@ -239,7 +239,7 @@ func jsonifyAmpObject(ao *analytics.AmpObject) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logAMP }{ @@ -263,7 +263,7 @@ func jsonifyNotificationEventObject(ne *analytics.NotificationEvent) string { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Type RequestType `json:"type"` *logNotificationEvent }{ diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 9843a8ab108..f2f81bcdf77 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/openrtb/v19/openrtb2" ) diff --git a/analytics/filesystem/model.go b/analytics/filesystem/model.go index 9fc7a6e19a2..61987ed3b53 100644 --- a/analytics/filesystem/model.go +++ b/analytics/filesystem/model.go @@ -4,10 +4,10 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/configupdate.go b/analytics/pubstack/configupdate.go index 622161a04f2..0ecaaef05c3 100644 --- a/analytics/pubstack/configupdate.go +++ b/analytics/pubstack/configupdate.go @@ -6,7 +6,7 @@ import ( "net/url" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" ) // ConfigUpdateTask publishes configurations until the stop channel is signaled. diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go index 368c79e3f6a..c516cb56ae8 100644 --- a/analytics/pubstack/helpers/json.go +++ b/analytics/pubstack/helpers/json.go @@ -1,11 +1,11 @@ package helpers import ( - "encoding/json" "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, error) { @@ -26,7 +26,7 @@ func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, er } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logAuction }{ @@ -59,7 +59,7 @@ func JsonifyVideoObject(vo *analytics.VideoObject, scope string) ([]byte, error) } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logVideo }{ @@ -84,7 +84,7 @@ func JsonifyCookieSync(cso *analytics.CookieSyncObject, scope string) ([]byte, e } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logUserSync }{ @@ -111,7 +111,7 @@ func JsonifySetUIDObject(so *analytics.SetUIDObject, scope string) ([]byte, erro } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logSetUID }{ @@ -145,7 +145,7 @@ func JsonifyAmpObject(ao *analytics.AmpObject, scope string) ([]byte, error) { } } - b, err := json.Marshal(&struct { + b, err := jsonutil.Marshal(&struct { Scope string `json:"scope"` *logAMP }{ diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 07ead724929..7ec46e53f83 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/v2/analytics" "github.com/stretchr/testify/assert" ) diff --git a/analytics/pubstack/helpers/model.go b/analytics/pubstack/helpers/model.go index 91b86d7fc86..1d6ede1c59b 100644 --- a/analytics/pubstack/helpers/model.go +++ b/analytics/pubstack/helpers/model.go @@ -4,10 +4,10 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index 987c935f884..535118c0000 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -12,9 +12,9 @@ import ( "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" - "github.com/prebid/prebid-server/analytics/pubstack/helpers" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/analytics/pubstack/eventchannel" + "github.com/prebid/prebid-server/v2/analytics/pubstack/helpers" ) type Configuration struct { @@ -50,7 +50,7 @@ type PubstackModule struct { clock clock.Clock } -func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { +func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.Module, error) { configUpdateTask, err := NewConfigUpdateHttpTask( client, scope, @@ -63,7 +63,7 @@ func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, return NewModuleWithConfigTask(client, scope, endpoint, maxEventCount, maxByteSize, maxTime, configUpdateTask, clock) } -func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.PBSAnalyticsModule, error) { +func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.Module, error) { glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) // parse args diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 504a1cfe17e..911de4c6959 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -50,47 +50,47 @@ func TestNewModuleSuccess(t *testing.T) { tests := []struct { description string feature string - logObject func(analytics.PBSAnalyticsModule) + logObject func(analytics.Module) }{ { description: "auction events are only published when logging an auction object with auction feature on", feature: auction, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAuctionObject(&analytics.AuctionObject{Status: http.StatusOK}) }, }, { description: "AMP events are only published when logging an AMP object with AMP feature on", feature: amp, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAmpObject(&analytics.AmpObject{Status: http.StatusOK}) }, }, { description: "video events are only published when logging a video object with video feature on", feature: video, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogVideoObject(&analytics.VideoObject{Status: http.StatusOK}) }, }, { description: "cookie events are only published when logging a cookie object with cookie feature on", feature: cookieSync, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogCookieSyncObject(&analytics.CookieSyncObject{Status: http.StatusOK}) }, }, { description: "setUID events are only published when logging a setUID object with setUID feature on", feature: setUID, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogSetUIDObject(&analytics.SetUIDObject{Status: http.StatusOK}) }, }, { description: "Ignore excluded fields from marshal", feature: auction, - logObject: func(module analytics.PBSAnalyticsModule) { + logObject: func(module analytics.Module) { module.LogAuctionObject(&analytics.AuctionObject{ RequestWrapper: &openrtb_ext.RequestWrapper{}, SeatNonBid: []openrtb_ext.SeatNonBid{ diff --git a/analytics/runner.go b/analytics/runner.go new file mode 100644 index 00000000000..7a2c56f77dd --- /dev/null +++ b/analytics/runner.go @@ -0,0 +1,14 @@ +package analytics + +import ( + "github.com/prebid/prebid-server/v2/privacy" +) + +type Runner interface { + LogAuctionObject(*AuctionObject, privacy.ActivityControl) + LogVideoObject(*VideoObject, privacy.ActivityControl) + LogCookieSyncObject(*CookieSyncObject) + LogSetUIDObject(*SetUIDObject) + LogAmpObject(*AmpObject, privacy.ActivityControl) + LogNotificationEventObject(*NotificationEvent, privacy.ActivityControl) +} diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 4fa3b737b16..16b8305a01a 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -3,8 +3,8 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index 27daa9c73d6..a1fa9f7c486 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -223,12 +223,12 @@ type mockConversions struct { mock.Mock } -func (m mockConversions) GetRate(from string, to string) (float64, error) { +func (m *mockConversions) GetRate(from string, to string) (float64, error) { args := m.Called(from, to) return args.Get(0).(float64), args.Error(1) } -func (m mockConversions) GetRates() *map[string]map[string]float64 { +func (m *mockConversions) GetRates() *map[string]map[string]float64 { args := m.Called() return args.Get(0).(*map[string]map[string]float64) } diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index bccb3bc86cf..f028d78616a 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -1,8 +1,8 @@ package bidadjustment import ( - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 263a782130e..9ae1b477e8f 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index c0ae3d4a27b..f34ef48ba49 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -3,7 +3,7 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Validate checks whether all provided bid adjustments are valid or not against the requirements defined in the issue diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index a0b4eb436eb..caf4188bb5e 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -3,7 +3,7 @@ package bidadjustment import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/config/account.go b/config/account.go index 8beff9b6569..ee131873e35 100644 --- a/config/account.go +++ b/config/account.go @@ -7,7 +7,8 @@ import ( "strings" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/iputil" ) // ChannelType enumerates the values of integrations Prebid Server can configure for an account @@ -19,6 +20,7 @@ const ( ChannelApp ChannelType = "app" ChannelVideo ChannelType = "video" ChannelWeb ChannelType = "web" + ChannelDOOH ChannelType = "dooh" ) // Account represents a publisher account configuration @@ -26,7 +28,6 @@ type Account struct { ID string `mapstructure:"id" json:"id"` Disabled bool `mapstructure:"disabled" json:"disabled"` CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"` - EventsEnabled *bool `mapstructure:"events_enabled" json:"events_enabled"` // Deprecated: Use events.enabled instead. CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"` GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` @@ -52,23 +53,35 @@ type CookieSync struct { // AccountCCPA represents account-specific CCPA configuration type AccountCCPA struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` - ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` } type AccountPriceFloors struct { - Enabled bool `mapstructure:"enabled" json:"enabled"` - EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` - AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` - EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` - UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` - MaxRule int `mapstructure:"max_rules" json:"max_rules"` - MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Enabled bool `mapstructure:"enabled" json:"enabled"` + EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` + AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` + EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` + UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` + MaxRule int `mapstructure:"max_rules" json:"max_rules"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Fetcher AccountFloorFetch `mapstructure:"fetch" json:"fetch"` } -func (pf *AccountPriceFloors) validate(errs []error) []error { +// AccountFloorFetch defines the configuration for dynamic floors fetching. +type AccountFloorFetch struct { + Enabled bool `mapstructure:"enabled" json:"enabled"` + URL string `mapstructure:"url" json:"url"` + Timeout int `mapstructure:"timeout_ms" json:"timeout_ms"` + MaxFileSizeKB int `mapstructure:"max_file_size_kb" json:"max_file_size_kb"` + MaxRules int `mapstructure:"max_rules" json:"max_rules"` + MaxAge int `mapstructure:"max_age_sec" json:"max_age_sec"` + Period int `mapstructure:"period_sec" json:"period_sec"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + AccountID string `mapstructure:"accountID" json:"accountID"` +} +func (pf *AccountPriceFloors) validate(errs []error) []error { if pf.EnforceFloorsRate < 0 || pf.EnforceFloorsRate > 100 { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.enforce_floors_rate should be between 0 and 100`)) } @@ -81,25 +94,54 @@ func (pf *AccountPriceFloors) validate(errs []error) []error { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.max_schema_dims should be between 0 and 20`)) } + if pf.Fetcher.Period > pf.Fetcher.MaxAge { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec`)) + } + + if pf.Fetcher.Period < 300 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds`)) + } + + if pf.Fetcher.MaxAge < 600 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value`)) + } + + if !(pf.Fetcher.Timeout > 10 && pf.Fetcher.Timeout < 10000) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds`)) + } + + if pf.Fetcher.MaxRules < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0`)) + } + + if pf.Fetcher.MaxFileSizeKB < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0`)) + } + + if !(pf.Fetcher.MaxSchemaDims >= 0 && pf.Fetcher.MaxSchemaDims < 20) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20`)) + } + return errs } +func (pf *AccountPriceFloors) IsAdjustForBidAdjustmentEnabled() bool { + return pf.AdjustForBidAdjustment +} + // EnabledForChannelType indicates whether CCPA is turned on at the account level for the specified channel type // by using the channel type setting if defined or the general CCPA setting if defined; otherwise it returns nil func (a *AccountCCPA) EnabledForChannelType(channelType ChannelType) *bool { if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { return channelEnabled - } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { - return integrationEnabled } return a.Enabled } // AccountGDPR represents account-specific GDPR configuration type AccountGDPR struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountChannel `mapstructure:"integration_enabled" json:"integration_enabled"` - ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + ChannelEnabled AccountChannel `mapstructure:"channel_enabled" json:"channel_enabled"` // Array of basic enforcement vendors that is used to create the hash table so vendor names can be instantly accessed BasicEnforcementVendors []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"` BasicEnforcementVendorsMap map[string]struct{} @@ -124,8 +166,6 @@ type AccountGDPR struct { func (a *AccountGDPR) EnabledForChannelType(channelType ChannelType) *bool { if channelEnabled := a.ChannelEnabled.GetByChannelType(channelType); channelEnabled != nil { return channelEnabled - } else if integrationEnabled := a.IntegrationEnabled.GetByChannelType(channelType); integrationEnabled != nil { - return integrationEnabled } return a.Enabled } @@ -246,6 +286,7 @@ type AccountChannel struct { App *bool `mapstructure:"app" json:"app,omitempty"` Video *bool `mapstructure:"video" json:"video,omitempty"` Web *bool `mapstructure:"web" json:"web,omitempty"` + DOOH *bool `mapstructure:"dooh" json:"dooh,omitempty"` } // GetByChannelType looks up the account integration enabled setting for the specified channel type @@ -261,6 +302,8 @@ func (a *AccountChannel) GetByChannelType(channelType ChannelType) *bool { channelEnabled = a.Video case ChannelWeb: channelEnabled = a.Web + case ChannelDOOH: + channelEnabled = a.DOOH } return channelEnabled @@ -290,10 +333,32 @@ func (m AccountModules) ModuleConfig(id string) (json.RawMessage, error) { return m[vendor][module], nil } -func (a *AccountChannel) IsSet() bool { - return a.AMP != nil || a.App != nil || a.Video != nil || a.Web != nil +type AccountPrivacy struct { + AllowActivities *AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` + IPv6Config IPv6 `mapstructure:"ipv6" json:"ipv6"` + IPv4Config IPv4 `mapstructure:"ipv4" json:"ipv4"` +} + +type IPv6 struct { + AnonKeepBits int `mapstructure:"anon_keep_bits" json:"anon_keep_bits"` } -type AccountPrivacy struct { - AllowActivities AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` +type IPv4 struct { + AnonKeepBits int `mapstructure:"anon_keep_bits" json:"anon_keep_bits"` +} + +func (ip *IPv6) Validate(errs []error) []error { + if ip.AnonKeepBits > iputil.IPv6BitSize || ip.AnonKeepBits < 0 { + err := fmt.Errorf("bits cannot exceed %d in ipv6 address, or be less than 0", iputil.IPv6BitSize) + errs = append(errs, err) + } + return errs +} + +func (ip *IPv4) Validate(errs []error) []error { + if ip.AnonKeepBits > iputil.IPv4BitSize || ip.AnonKeepBits < 0 { + err := fmt.Errorf("bits cannot exceed %d in ipv4 address, or be less than 0", iputil.IPv4BitSize) + errs = append(errs, err) + } + return errs } diff --git a/config/account_test.go b/config/account_test.go index 20c6053246e..65e7f3e8716 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -14,68 +14,46 @@ func TestAccountGDPREnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveChannelType ChannelType - giveGDPREnabled *bool - giveWebGDPREnabled *bool - giveWebGDPREnabledForIntegration *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveGDPREnabled *bool + giveWebGDPREnabled *bool + wantEnabled *bool }{ { - description: "GDPR Web channel enabled, general GDPR disabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: &trueValue, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "GDPR Web channel enabled, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: &trueValue, + wantEnabled: &trueValue, }, { - description: "GDPR Web channel disabled, general GDPR enabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: &falseValue, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "GDPR Web channel disabled, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: &falseValue, + wantEnabled: &falseValue, }, { - description: "GDPR Web channel unspecified, general GDPR disabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "GDPR Web channel unspecified, general GDPR disabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &falseValue, + giveWebGDPREnabled: nil, + wantEnabled: &falseValue, }, { - description: "GDPR Web channel unspecified, general GDPR enabled", - giveChannelType: ChannelWeb, - giveGDPREnabled: &trueValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "GDPR Web channel unspecified, general GDPR enabled", + giveChannelType: ChannelWeb, + giveGDPREnabled: &trueValue, + giveWebGDPREnabled: nil, + wantEnabled: &trueValue, }, { - description: "GDPR Web channel unspecified, general GDPR unspecified", - giveChannelType: ChannelWeb, - giveGDPREnabled: nil, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: nil, - wantEnabled: nil, - }, - { - description: "Inegration Enabled is set, and channel enabled isn't", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: nil, - giveWebGDPREnabledForIntegration: &trueValue, - wantEnabled: &trueValue, - }, - { - description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", - giveChannelType: ChannelWeb, - giveGDPREnabled: &falseValue, - giveWebGDPREnabled: &trueValue, - giveWebGDPREnabledForIntegration: &falseValue, - wantEnabled: &trueValue, + description: "GDPR Web channel unspecified, general GDPR unspecified", + giveChannelType: ChannelWeb, + giveGDPREnabled: nil, + giveWebGDPREnabled: nil, + wantEnabled: nil, }, } @@ -86,9 +64,6 @@ func TestAccountGDPREnabledForChannelType(t *testing.T) { ChannelEnabled: AccountChannel{ Web: tt.giveWebGDPREnabled, }, - IntegrationEnabled: AccountChannel{ - Web: tt.giveWebGDPREnabledForIntegration, - }, }, } @@ -107,68 +82,46 @@ func TestAccountCCPAEnabledForChannelType(t *testing.T) { trueValue, falseValue := true, false tests := []struct { - description string - giveChannelType ChannelType - giveCCPAEnabled *bool - giveWebCCPAEnabled *bool - giveWebCCPAEnabledForIntegration *bool - wantEnabled *bool + description string + giveChannelType ChannelType + giveCCPAEnabled *bool + giveWebCCPAEnabled *bool + wantEnabled *bool }{ { - description: "CCPA Web channel enabled, general CCPA disabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: &trueValue, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &trueValue, - }, - { - description: "CCPA Web channel disabled, general CCPA enabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: &falseValue, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "CCPA Web channel enabled, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: &trueValue, + wantEnabled: &trueValue, }, { - description: "CCPA Web channel unspecified, general CCPA disabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &falseValue, + description: "CCPA Web channel disabled, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: &falseValue, + wantEnabled: &falseValue, }, { - description: "CCPA Web channel unspecified, general CCPA enabled", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &trueValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: &trueValue, + description: "CCPA Web channel unspecified, general CCPA disabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &falseValue, + giveWebCCPAEnabled: nil, + wantEnabled: &falseValue, }, { - description: "CCPA Web channel unspecified, general CCPA unspecified", - giveChannelType: ChannelWeb, - giveCCPAEnabled: nil, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: nil, - wantEnabled: nil, + description: "CCPA Web channel unspecified, general CCPA enabled", + giveChannelType: ChannelWeb, + giveCCPAEnabled: &trueValue, + giveWebCCPAEnabled: nil, + wantEnabled: &trueValue, }, { - description: "Inegration Enabled is set, and channel enabled isn't", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: nil, - giveWebCCPAEnabledForIntegration: &trueValue, - wantEnabled: &trueValue, - }, - { - description: "Inegration Enabled is set, and channel enabled is set, channel should have precedence", - giveChannelType: ChannelWeb, - giveCCPAEnabled: &falseValue, - giveWebCCPAEnabled: &trueValue, - giveWebCCPAEnabledForIntegration: &falseValue, - wantEnabled: &trueValue, + description: "CCPA Web channel unspecified, general CCPA unspecified", + giveChannelType: ChannelWeb, + giveCCPAEnabled: nil, + giveWebCCPAEnabled: nil, + wantEnabled: nil, }, } @@ -179,9 +132,6 @@ func TestAccountCCPAEnabledForChannelType(t *testing.T) { ChannelEnabled: AccountChannel{ Web: tt.giveWebCCPAEnabled, }, - IntegrationEnabled: AccountChannel{ - Web: tt.giveWebCCPAEnabledForIntegration, - }, }, } @@ -205,6 +155,7 @@ func TestAccountChannelGetByChannelType(t *testing.T) { giveAppEnabled *bool giveVideoEnabled *bool giveWebEnabled *bool + giveDOOHEnabled *bool giveChannelType ChannelType wantEnabled *bool }{ @@ -276,6 +227,23 @@ func TestAccountChannelGetByChannelType(t *testing.T) { giveChannelType: ChannelWeb, wantEnabled: &trueValue, }, + { + description: "DOOH channel setting unspecified, returns nil", + giveChannelType: ChannelDOOH, + wantEnabled: nil, + }, + { + description: "DOOH channel disabled, returns false", + giveDOOHEnabled: &falseValue, + giveChannelType: ChannelDOOH, + wantEnabled: &falseValue, + }, + { + description: "DOOH channel enabled, returns true", + giveDOOHEnabled: &trueValue, + giveChannelType: ChannelDOOH, + wantEnabled: &trueValue, + }, } for _, tt := range tests { @@ -284,6 +252,7 @@ func TestAccountChannelGetByChannelType(t *testing.T) { App: tt.giveAppEnabled, Video: tt.giveVideoEnabled, Web: tt.giveWebEnabled, + DOOH: tt.giveDOOHEnabled, } result := accountChannel.GetByChannelType(tt.giveChannelType) @@ -825,41 +794,7 @@ func TestModulesGetConfig(t *testing.T) { } } -func TestAccountChannelIsSet(t *testing.T) { - trueBool := true - falseBool := false - - testCases := []struct { - name string - givenAccountChannel *AccountChannel - expected bool - }{ - { - name: "AccountChannelSetAllFields", - givenAccountChannel: &AccountChannel{AMP: &trueBool, App: &falseBool, Video: &falseBool, Web: &falseBool}, - expected: true, - }, - { - name: "AccountChannelNotSet", - givenAccountChannel: &AccountChannel{}, - expected: false, - }, - { - name: "AccountChannelSetAmpOnly", - givenAccountChannel: &AccountChannel{AMP: &trueBool}, - expected: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, test.givenAccountChannel.IsSet()) - }) - } -} - func TestAccountPriceFloorsValidate(t *testing.T) { - tests := []struct { description string pf *AccountPriceFloors @@ -871,12 +806,22 @@ func TestAccountPriceFloorsValidate(t *testing.T) { EnforceFloorsRate: 100, MaxRule: 200, MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, }, { description: "Invalid configuration: EnforceFloorRate:110", pf: &AccountPriceFloors{ EnforceFloorsRate: 110, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -884,6 +829,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: EnforceFloorRate:-10", pf: &AccountPriceFloors{ EnforceFloorsRate: -10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -891,6 +841,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxRule:-20", pf: &AccountPriceFloors{ MaxRule: -20, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_rules should be between 0 and 2147483647")}, }, @@ -898,9 +853,116 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxSchemaDims:100", pf: &AccountPriceFloors{ MaxSchemaDims: 100, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_schema_dims should be between 0 and 20")}, }, + { + description: "Invalid period for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 100, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds")}, + }, + { + description: "Invalid max age for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 500, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value")}, + }, + { + description: "Period is greater than max age", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 700, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec")}, + }, + { + description: "Invalid timeout", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 4, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds")}, + }, + { + description: "Invalid Max Rules", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxRules: -2, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0")}, + }, + { + description: "Invalid Max File size", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: -1, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0")}, + }, + { + description: "Invalid max_schema_dims", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: 10, + MaxSchemaDims: 40, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20")}, + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { @@ -910,3 +972,49 @@ func TestAccountPriceFloorsValidate(t *testing.T) { }) } } + +func TestIPMaskingValidate(t *testing.T) { + tests := []struct { + name string + privacy AccountPrivacy + want []error + }{ + { + name: "valid", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: 1}, + IPv6Config: IPv6{AnonKeepBits: 0}, + }, + }, + { + name: "invalid", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: -100}, + IPv6Config: IPv6{AnonKeepBits: -200}, + }, + want: []error{ + errors.New("bits cannot exceed 32 in ipv4 address, or be less than 0"), + errors.New("bits cannot exceed 128 in ipv6 address, or be less than 0"), + }, + }, + { + name: "mixed", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: 10}, + IPv6Config: IPv6{AnonKeepBits: -10}, + }, + want: []error{ + errors.New("bits cannot exceed 128 in ipv6 address, or be less than 0"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var errs []error + errs = tt.privacy.IPv4Config.Validate(errs) + errs = tt.privacy.IPv6Config.Validate(errs) + assert.ElementsMatch(t, errs, tt.want) + }) + } +} diff --git a/config/activity.go b/config/activity.go index 987cbe84a2d..5bddc7c6405 100644 --- a/config/activity.go +++ b/config/activity.go @@ -8,16 +8,17 @@ type AllowActivities struct { TransmitUserFPD Activity `mapstructure:"transmitUfpd" json:"transmitUfpd"` TransmitPreciseGeo Activity `mapstructure:"transmitPreciseGeo" json:"transmitPreciseGeo"` TransmitUniqueRequestIds Activity `mapstructure:"transmitUniqueRequestIds" json:"transmitUniqueRequestIds"` + TransmitTids Activity `mapstructure:"transmitTid" json:"transmitTid"` } type Activity struct { Default *bool `mapstructure:"default" json:"default"` Rules []ActivityRule `mapstructure:"rules" json:"rules"` - Allow bool `mapstructure:"allow" json:"allow"` } type ActivityRule struct { Condition ActivityCondition `mapstructure:"condition" json:"condition"` + Allow bool `mapstructure:"allow" json:"allow"` } type ActivityCondition struct { diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 6f62488c878..d78f5722552 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -9,10 +9,8 @@ import ( "strings" "text/template" - "github.com/golang/glog" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" validator "github.com/asaskevich/govalidator" "gopkg.in/yaml.v3" @@ -23,6 +21,7 @@ type BidderInfos map[string]BidderInfo // BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information. type BidderInfo struct { + AliasOf string `yaml:"aliasOf" mapstructure:"aliasOf"` Disabled bool `yaml:"disabled" mapstructure:"disabled"` Endpoint string `yaml:"endpoint" mapstructure:"endpoint"` ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"` @@ -37,9 +36,6 @@ type BidderInfo struct { Experiment BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` - // needed for backwards compatibility - UserSyncURL string `yaml:"usersync_url" mapstructure:"usersync_url"` - // needed for Rubicon XAPI AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` @@ -51,6 +47,13 @@ type BidderInfo struct { OpenRTB *OpenRTBInfo `yaml:"openrtb" mapstructure:"openrtb"` } +type aliasNillableFields struct { + Disabled *bool `yaml:"disabled" mapstructure:"disabled"` + ModifyingVastXmlAllowed *bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` + Experiment *BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` + XAPI *AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` +} + // BidderInfoExperiment specifies non-production ready feature config for a bidder type BidderInfoExperiment struct { AdsCert BidderAdsCert `yaml:"adsCert" mapstructure:"adsCert"` @@ -70,6 +73,7 @@ type MaintainerInfo struct { type CapabilitiesInfo struct { App *PlatformInfo `yaml:"app" mapstructure:"app"` Site *PlatformInfo `yaml:"site" mapstructure:"site"` + DOOH *PlatformInfo `yaml:"dooh" mapstructure:"dooh"` } // PlatformInfo specifies the supported media types for a bidder. @@ -120,6 +124,20 @@ type Syncer struct { // SupportCORS identifies if CORS is supported for the user syncing endpoints. SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` + + // FormatOverride allows a bidder to override their callback type "b" for iframe, "i" for redirect + FormatOverride string `yaml:"formatOverride" mapstructure:"format_override"` + + // Enabled signifies whether a bidder is enabled/disabled for user sync + Enabled *bool `yaml:"enabled" mapstructure:"enabled"` + + // SkipWhen allows bidders to specify when they don't want to sync + SkipWhen *SkipWhen `yaml:"skipwhen" mapstructure:"skipwhen"` +} + +type SkipWhen struct { + GDPR bool `yaml:"gdpr" mapstructure:"gdpr"` + GPPSID []string `yaml:"gpp_sid" mapstructure:"gpp_sid"` } // SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint @@ -161,7 +179,7 @@ type SyncerEndpoint struct { // startup: // // {{.ExternalURL}} - This will be replaced with the host server's externally reachable http path. - // {{.SyncerKey}} - This will be replaced with the syncer key. + // {{.BidderName}} - This will be replaced with the bidder name. // {{.SyncType}} - This will be replaced with the sync type, either 'b' for iframe syncs or 'i' // for redirect/image syncs. // {{.UserMacro}} - This will be replaced with the bidder server's user id macro. @@ -191,6 +209,11 @@ type InfoReaderFromDisk struct { Path string } +const ( + SyncResponseFormatIFrame = "b" // b = blank HTML response + SyncResponseFormatRedirect = "i" // i = image response +) + func (r InfoReaderFromDisk) Read() (map[string][]byte, error) { bidderConfigs, err := os.ReadDir(r.Path) if err != nil { @@ -228,24 +251,113 @@ func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (ope return nil, fmt.Errorf("error loading bidders data") } - infos := BidderInfos{} - + bidderInfos := BidderInfos{} + aliasNillableFieldsByBidder := map[string]aliasNillableFields{} for fileName, data := range bidderConfigs { bidderName := strings.Split(fileName, ".") if len(bidderName) == 2 && bidderName[1] == "yaml" { - normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) - if !bidderNameExists { - return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName) - } info := BidderInfo{} if err := yaml.Unmarshal(data, &info); err != nil { return nil, fmt.Errorf("error parsing config for bidder %s: %v", fileName, err) } - infos[string(normalizedBidderName)] = info + //need to maintain nullable fields from BidderInfo struct into bidderInfoNullableFields + //to handle the default values in aliases yaml + if len(info.AliasOf) > 0 { + aliasFields := aliasNillableFields{} + if err := yaml.Unmarshal(data, &aliasFields); err != nil { + return nil, fmt.Errorf("error parsing config for aliased bidder %s: %v", fileName, err) + } + + //required for CoreBidderNames function to also return aliasBiddernames + if err := openrtb_ext.SetAliasBidderName(bidderName[0], openrtb_ext.BidderName(info.AliasOf)); err != nil { + return nil, err + } + + normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) + if !bidderNameExists { + return nil, fmt.Errorf("error parsing config for an alias %s: unknown bidder", fileName) + } + + aliasNillableFieldsByBidder[string(normalizedBidderName)] = aliasFields + bidderInfos[string(normalizedBidderName)] = info + } else { + normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) + if !bidderNameExists { + return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName) + } + + bidderInfos[string(normalizedBidderName)] = info + } + } + } + return processBidderAliases(aliasNillableFieldsByBidder, bidderInfos) +} + +func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFields, bidderInfos BidderInfos) (BidderInfos, error) { + for bidderName, alias := range aliasNillableFieldsByBidder { + aliasBidderInfo, ok := bidderInfos[bidderName] + if !ok { + return nil, fmt.Errorf("bidder info not found for an alias: %s", bidderName) + } + if err := validateAliases(aliasBidderInfo, bidderInfos, bidderName); err != nil { + return nil, err + } + + parentBidderInfo := bidderInfos[aliasBidderInfo.AliasOf] + if aliasBidderInfo.AppSecret == "" { + aliasBidderInfo.AppSecret = parentBidderInfo.AppSecret + } + if aliasBidderInfo.Capabilities == nil { + aliasBidderInfo.Capabilities = parentBidderInfo.Capabilities + } + if aliasBidderInfo.Debug == nil { + aliasBidderInfo.Debug = parentBidderInfo.Debug + } + if aliasBidderInfo.Endpoint == "" { + aliasBidderInfo.Endpoint = parentBidderInfo.Endpoint + } + if aliasBidderInfo.EndpointCompression == "" { + aliasBidderInfo.EndpointCompression = parentBidderInfo.EndpointCompression + } + if aliasBidderInfo.ExtraAdapterInfo == "" { + aliasBidderInfo.ExtraAdapterInfo = parentBidderInfo.ExtraAdapterInfo } + if aliasBidderInfo.GVLVendorID == 0 { + aliasBidderInfo.GVLVendorID = parentBidderInfo.GVLVendorID + } + if aliasBidderInfo.Maintainer == nil { + aliasBidderInfo.Maintainer = parentBidderInfo.Maintainer + } + if aliasBidderInfo.OpenRTB == nil { + aliasBidderInfo.OpenRTB = parentBidderInfo.OpenRTB + } + if aliasBidderInfo.PlatformID == "" { + aliasBidderInfo.PlatformID = parentBidderInfo.PlatformID + } + if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer != nil { + syncerKey := aliasBidderInfo.AliasOf + if parentBidderInfo.Syncer.Key != "" { + syncerKey = parentBidderInfo.Syncer.Key + } + syncer := Syncer{Key: syncerKey} + aliasBidderInfo.Syncer = &syncer + } + if alias.Disabled == nil { + aliasBidderInfo.Disabled = parentBidderInfo.Disabled + } + if alias.Experiment == nil { + aliasBidderInfo.Experiment = parentBidderInfo.Experiment + } + if alias.ModifyingVastXmlAllowed == nil { + aliasBidderInfo.ModifyingVastXmlAllowed = parentBidderInfo.ModifyingVastXmlAllowed + } + if alias.XAPI == nil { + aliasBidderInfo.XAPI = parentBidderInfo.XAPI + } + bidderInfos[bidderName] = aliasBidderInfo } - return infos, nil + return bidderInfos, nil } // ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. @@ -266,20 +378,31 @@ func (infos BidderInfos) validate(errs []error) []error { if bidder.IsEnabled() { errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs) - validateInfoErr := validateInfo(bidder, bidderName) - if validateInfoErr != nil { - errs = append(errs, validateInfoErr) + if err := validateInfo(bidder, infos, bidderName); err != nil { + errs = append(errs, err) } - validateSyncerErr := validateSyncer(bidder) - if validateSyncerErr != nil { - errs = append(errs, validateSyncerErr) + if err := validateSyncer(bidder); err != nil { + errs = append(errs, err) } } } return errs } +func validateAliases(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error { + if len(aliasBidderInfo.AliasOf) > 0 { + if parentBidder, ok := infos[aliasBidderInfo.AliasOf]; ok { + if len(parentBidder.AliasOf) > 0 { + return fmt.Errorf("bidder: %s cannot be an alias of an alias: %s", aliasBidderInfo.AliasOf, bidderName) + } + } else { + return fmt.Errorf("bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName) + } + } + return nil +} + var testEndpointTemplateParams = macros.EndpointTemplateParams{ Host: "anyHost", PublisherID: "anyPublisherID", @@ -322,14 +445,18 @@ func validateAdapterEndpoint(endpoint string, bidderName string, errs []error) [ return errs } -func validateInfo(info BidderInfo, bidderName string) error { - if err := validateMaintainer(info.Maintainer, bidderName); err != nil { +func validateInfo(bidder BidderInfo, infos BidderInfos, bidderName string) error { + if err := validateMaintainer(bidder.Maintainer, bidderName); err != nil { return err } - if err := validateCapabilities(info.Capabilities, bidderName); err != nil { + if err := validateCapabilities(bidder.Capabilities, bidderName); err != nil { return err } - + if len(bidder.AliasOf) > 0 { + if err := validateAliasCapabilities(bidder, infos, bidderName); err != nil { + return err + } + } return nil } @@ -340,13 +467,67 @@ func validateMaintainer(info *MaintainerInfo, bidderName string) error { return nil } +func validateAliasCapabilities(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error { + parentBidder, parentFound := infos[aliasBidderInfo.AliasOf] + if !parentFound { + return fmt.Errorf("parent bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName) + } + + if aliasBidderInfo.Capabilities != nil { + if parentBidder.Capabilities == nil { + return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) + } + + if (aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App == nil) || + (aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site == nil) || + (aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH == nil) { + return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) + } + + if aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site != nil { + if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.Site, *aliasBidderInfo.Capabilities.Site, bidderName, aliasBidderInfo.AliasOf); err != nil { + return err + } + } + + if aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App != nil { + if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.App, *aliasBidderInfo.Capabilities.App, bidderName, aliasBidderInfo.AliasOf); err != nil { + return err + } + } + + if aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH != nil { + if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.DOOH, *aliasBidderInfo.Capabilities.DOOH, bidderName, aliasBidderInfo.AliasOf); err != nil { + return err + } + } + } + + return nil +} + +func isAliasPlatformInfoSubsetOfParent(parentInfo PlatformInfo, aliasInfo PlatformInfo, bidderName string, parentBidderName string) error { + parentMediaTypes := make(map[openrtb_ext.BidType]struct{}) + for _, info := range parentInfo.MediaTypes { + parentMediaTypes[info] = struct{}{} + } + + for _, info := range aliasInfo.MediaTypes { + if _, found := parentMediaTypes[info]; !found { + return fmt.Errorf("mediaTypes for alias: %s should be a subset of MediaTypes for parent bidder: %s", bidderName, parentBidderName) + } + } + + return nil +} + func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { if info == nil { return fmt.Errorf("missing required field: capabilities for adapter: %s", bidderName) } - if info.App == nil && info.Site == nil { - return fmt.Errorf("at least one of capabilities.site or capabilities.app must exist for adapter: %s", bidderName) + if info.App == nil && info.Site == nil && info.DOOH == nil { + return fmt.Errorf("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: %s", bidderName) } if info.App != nil { @@ -357,9 +538,16 @@ func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { if info.Site != nil { if err := validatePlatformInfo(info.Site); err != nil { - return fmt.Errorf("capabilities.site failed validation: %v, for adapter: %s", err, bidderName) + return fmt.Errorf("capabilities.site failed validation: %v for adapter: %s", err, bidderName) + } + } + + if info.DOOH != nil { + if err := validatePlatformInfo(info.DOOH); err != nil { + return fmt.Errorf("capabilities.dooh failed validation: %v for adapter: %s", err, bidderName) } } + return nil } @@ -382,6 +570,10 @@ func validateSyncer(bidderInfo BidderInfo) error { return nil } + if bidderInfo.Syncer.FormatOverride != SyncResponseFormatIFrame && bidderInfo.Syncer.FormatOverride != SyncResponseFormatRedirect && bidderInfo.Syncer.FormatOverride != "" { + return fmt.Errorf("syncer could not be created, invalid format override value: %s", bidderInfo.Syncer.FormatOverride) + } + for _, supports := range bidderInfo.Syncer.Supports { if !strings.EqualFold(supports, "iframe") && !strings.EqualFold(supports, "redirect") { return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", supports) @@ -391,103 +583,79 @@ func validateSyncer(bidderInfo BidderInfo) error { return nil } -func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { - for bidderName, bidderInfo := range configBidderInfos { - normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName) - if !bidderNameExists { +func applyBidderInfoConfigOverrides(configBidderInfos nillableFieldBidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { + mergedBidderInfos := make(map[string]BidderInfo, len(fsBidderInfos)) + + for bidderName, configBidderInfo := range configBidderInfos { + normalizedBidderName, exists := normalizeBidderName(bidderName) + if !exists { return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName) } - if fsBidderCfg, exists := fsBidderInfos[string(normalizedBidderName)]; exists { - bidderInfo.Syncer = bidderInfo.Syncer.Override(fsBidderCfg.Syncer) - - if bidderInfo.Endpoint == "" && len(fsBidderCfg.Endpoint) > 0 { - bidderInfo.Endpoint = fsBidderCfg.Endpoint - } - if bidderInfo.ExtraAdapterInfo == "" && len(fsBidderCfg.ExtraAdapterInfo) > 0 { - bidderInfo.ExtraAdapterInfo = fsBidderCfg.ExtraAdapterInfo - } - if bidderInfo.Maintainer == nil && fsBidderCfg.Maintainer != nil { - bidderInfo.Maintainer = fsBidderCfg.Maintainer - } - if bidderInfo.Capabilities == nil && fsBidderCfg.Capabilities != nil { - bidderInfo.Capabilities = fsBidderCfg.Capabilities - } - if bidderInfo.Debug == nil && fsBidderCfg.Debug != nil { - bidderInfo.Debug = fsBidderCfg.Debug - } - if bidderInfo.GVLVendorID == 0 && fsBidderCfg.GVLVendorID > 0 { - bidderInfo.GVLVendorID = fsBidderCfg.GVLVendorID - } - if bidderInfo.XAPI.Username == "" && fsBidderCfg.XAPI.Username != "" { - bidderInfo.XAPI.Username = fsBidderCfg.XAPI.Username - } - if bidderInfo.XAPI.Password == "" && fsBidderCfg.XAPI.Password != "" { - bidderInfo.XAPI.Password = fsBidderCfg.XAPI.Password - } - if bidderInfo.XAPI.Tracker == "" && fsBidderCfg.XAPI.Tracker != "" { - bidderInfo.XAPI.Tracker = fsBidderCfg.XAPI.Tracker - } - if bidderInfo.PlatformID == "" && fsBidderCfg.PlatformID != "" { - bidderInfo.PlatformID = fsBidderCfg.PlatformID - } - if bidderInfo.AppSecret == "" && fsBidderCfg.AppSecret != "" { - bidderInfo.AppSecret = fsBidderCfg.AppSecret - } - if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" { - bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression - } - - // validate and try to apply the legacy usersync_url configuration in attempt to provide - // an easier upgrade path. be warned, this will break if the bidder adds a second syncer - // type and will eventually be removed after we've given hosts enough time to upgrade to - // the new config. - if bidderInfo.UserSyncURL != "" { - if fsBidderCfg.Syncer == nil { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define a user sync", strings.ToLower(bidderName)) - } - - endpointsCount := 0 - if bidderInfo.Syncer.IFrame != nil { - bidderInfo.Syncer.IFrame.URL = bidderInfo.UserSyncURL - endpointsCount++ - } - if bidderInfo.Syncer.Redirect != nil { - bidderInfo.Syncer.Redirect.URL = bidderInfo.UserSyncURL - endpointsCount++ - } - - // use Supports as a hint if there are no good defaults provided - if endpointsCount == 0 { - if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "iframe") { - bidderInfo.Syncer.IFrame = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} - endpointsCount++ - } - if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "redirect") { - bidderInfo.Syncer.Redirect = &SyncerEndpoint{URL: bidderInfo.UserSyncURL} - endpointsCount++ - } - } - - if endpointsCount == 0 { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", strings.ToLower(bidderName)) - } - - // if the bidder defines both an iframe and redirect endpoint, we can't be sure which config value to - // override, and it wouldn't be both. this is a fatal configuration error. - if endpointsCount > 1 { - return nil, fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", strings.ToLower(bidderName)) - } + fsBidderInfo, exists := fsBidderInfos[string(normalizedBidderName)] + if !exists { + return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + } - // provide a warning that this compatibility layer is temporary - glog.Warningf("adapters.%s.usersync_url is deprecated and will be removed in a future version, please update to the latest user sync config values", strings.ToLower(bidderName)) - } + mergedBidderInfo := fsBidderInfo + mergedBidderInfo.Syncer = configBidderInfo.bidderInfo.Syncer.Override(fsBidderInfo.Syncer) + if len(configBidderInfo.bidderInfo.Endpoint) > 0 { + mergedBidderInfo.Endpoint = configBidderInfo.bidderInfo.Endpoint + } + if len(configBidderInfo.bidderInfo.ExtraAdapterInfo) > 0 { + mergedBidderInfo.ExtraAdapterInfo = configBidderInfo.bidderInfo.ExtraAdapterInfo + } + if configBidderInfo.bidderInfo.Maintainer != nil { + mergedBidderInfo.Maintainer = configBidderInfo.bidderInfo.Maintainer + } + if configBidderInfo.bidderInfo.Capabilities != nil { + mergedBidderInfo.Capabilities = configBidderInfo.bidderInfo.Capabilities + } + if configBidderInfo.bidderInfo.Debug != nil { + mergedBidderInfo.Debug = configBidderInfo.bidderInfo.Debug + } + if configBidderInfo.bidderInfo.GVLVendorID > 0 { + mergedBidderInfo.GVLVendorID = configBidderInfo.bidderInfo.GVLVendorID + } + if configBidderInfo.bidderInfo.XAPI.Username != "" { + mergedBidderInfo.XAPI.Username = configBidderInfo.bidderInfo.XAPI.Username + } + if configBidderInfo.bidderInfo.XAPI.Password != "" { + mergedBidderInfo.XAPI.Password = configBidderInfo.bidderInfo.XAPI.Password + } + if configBidderInfo.bidderInfo.XAPI.Tracker != "" { + mergedBidderInfo.XAPI.Tracker = configBidderInfo.bidderInfo.XAPI.Tracker + } + if configBidderInfo.bidderInfo.PlatformID != "" { + mergedBidderInfo.PlatformID = configBidderInfo.bidderInfo.PlatformID + } + if configBidderInfo.bidderInfo.AppSecret != "" { + mergedBidderInfo.AppSecret = configBidderInfo.bidderInfo.AppSecret + } + if configBidderInfo.nillableFields.Disabled != nil { + mergedBidderInfo.Disabled = configBidderInfo.bidderInfo.Disabled + } + if configBidderInfo.nillableFields.ModifyingVastXmlAllowed != nil { + mergedBidderInfo.ModifyingVastXmlAllowed = configBidderInfo.bidderInfo.ModifyingVastXmlAllowed + } + if configBidderInfo.bidderInfo.Experiment.AdsCert.Enabled == true { + mergedBidderInfo.Experiment.AdsCert.Enabled = true + } + if configBidderInfo.bidderInfo.EndpointCompression != "" { + mergedBidderInfo.EndpointCompression = configBidderInfo.bidderInfo.EndpointCompression + } + if configBidderInfo.bidderInfo.OpenRTB != nil { + mergedBidderInfo.OpenRTB = configBidderInfo.bidderInfo.OpenRTB + } - fsBidderInfos[string(normalizedBidderName)] = bidderInfo - } else { - return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + mergedBidderInfos[string(normalizedBidderName)] = mergedBidderInfo + } + for bidderName, fsBidderInfo := range fsBidderInfos { + if _, exists := mergedBidderInfos[bidderName]; !exists { + mergedBidderInfos[bidderName] = fsBidderInfo } } - return fsBidderInfos, nil + + return mergedBidderInfos, nil } // Override returns a new Syncer object where values in the original are replaced by non-empty/non-default diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 6005178c605..3305e34e88a 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -7,8 +7,9 @@ import ( "gopkg.in/yaml.v3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const testInfoFilesPathValid = "./test/bidder-info-valid" @@ -31,6 +32,9 @@ capabilities: - banner - video - native + dooh: + mediaTypes: + - banner modifyingVastXmlAllowed: true debug: allow: true @@ -42,6 +46,26 @@ endpointCompression: GZIP openrtb: version: 2.6 gpp-supported: true +endpoint: https://endpoint.com +disabled: false +extra_info: extra-info +app_secret: app-secret +platform_id: 123 +userSync: + key: foo + default: iframe + iframe: + url: https://foo.com/sync?mode=iframe&r={{.RedirectURL}} + redirectUrl: https://redirect/setuid/iframe + externalUrl: https://iframe.host + userMacro: UID +xapi: + username: uname + password: pwd + tracker: tracker +` +const testSimpleAliasYAML = ` +aliasOf: bidderA ` func TestLoadBidderInfoFromDisk(t *testing.T) { @@ -71,6 +95,9 @@ func TestLoadBidderInfoFromDisk(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, + }, }, Syncer: &Syncer{ Key: "foo", @@ -131,7 +158,120 @@ func TestProcessBidderInfo(t *testing.T) { expectedBidderInfos: nil, expectError: "error parsing config for bidder bidderA.yaml", }, + { + description: "Invalid alias name", + bidderInfos: map[string][]byte{ + "all.yaml": []byte(testSimpleAliasYAML), + }, + expectedBidderInfos: nil, + expectError: "alias all is a reserved bidder name and cannot be used", + }, + { + description: "Valid aliases", + bidderInfos: map[string][]byte{ + "bidderA.yaml": []byte(fullBidderYAMLConfig), + "bidderB.yaml": []byte(testSimpleAliasYAML), + }, + expectedBidderInfos: BidderInfos{ + "bidderA": BidderInfo{ + AppSecret: "app-secret", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + Debug: &DebugInfo{ + Allow: true, + }, + Disabled: false, + Endpoint: "https://endpoint.com", + EndpointCompression: "GZIP", + Experiment: BidderInfoExperiment{ + AdsCert: BidderAdsCert{ + Enabled: true, + }, + }, + ExtraAdapterInfo: "extra-info", + GVLVendorID: 42, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + ModifyingVastXmlAllowed: true, + OpenRTB: &OpenRTBInfo{ + GPPSupported: true, + Version: "2.6", + }, + PlatformID: "123", + Syncer: &Syncer{ + Key: "foo", + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://redirect/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "UID", + }, + }, + XAPI: AdapterXAPI{ + Username: "uname", + Password: "pwd", + Tracker: "tracker", + }, + }, + "bidderB": BidderInfo{ + AliasOf: "bidderA", + AppSecret: "app-secret", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + Debug: &DebugInfo{ + Allow: true, + }, + Disabled: false, + Endpoint: "https://endpoint.com", + EndpointCompression: "GZIP", + Experiment: BidderInfoExperiment{ + AdsCert: BidderAdsCert{ + Enabled: true, + }, + }, + ExtraAdapterInfo: "extra-info", + GVLVendorID: 42, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + ModifyingVastXmlAllowed: true, + OpenRTB: &OpenRTBInfo{ + GPPSupported: true, + Version: "2.6", + }, + PlatformID: "123", + Syncer: &Syncer{ + Key: "foo", + }, + XAPI: AdapterXAPI{ + Username: "uname", + Password: "pwd", + Tracker: "tracker", + }, + }, + }, + }, } + for _, test := range testCases { reader := StubInfoReader{test.bidderInfos} bidderInfos, err := processBidderInfos(reader, mockNormalizeBidderName) @@ -140,9 +280,220 @@ func TestProcessBidderInfo(t *testing.T) { } else { assert.Equal(t, test.expectedBidderInfos, bidderInfos, "incorrect bidder infos for test case: %s", test.description) } + } +} +func TestProcessAliasBidderInfo(t *testing.T) { + parentWithSyncerKey := BidderInfo{ + AppSecret: "app-secret", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + Debug: &DebugInfo{ + Allow: true, + }, + Disabled: false, + Endpoint: "https://endpoint.com", + EndpointCompression: "GZIP", + Experiment: BidderInfoExperiment{ + AdsCert: BidderAdsCert{ + Enabled: true, + }, + }, + ExtraAdapterInfo: "extra-info", + GVLVendorID: 42, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + ModifyingVastXmlAllowed: true, + OpenRTB: &OpenRTBInfo{ + GPPSupported: true, + Version: "2.6", + }, + PlatformID: "123", + Syncer: &Syncer{ + Key: "foo", + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://redirect/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "UID", + }, + }, + XAPI: AdapterXAPI{ + Username: "uname", + Password: "pwd", + Tracker: "tracker", + }, + } + aliasBidderInfo := BidderInfo{ + AppSecret: "alias-app-secret", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, + }, + Debug: &DebugInfo{ + Allow: false, + }, + Disabled: true, + Endpoint: "https://alias-endpoint.com", + EndpointCompression: "DEFAULT", + Experiment: BidderInfoExperiment{ + AdsCert: BidderAdsCert{ + Enabled: false, + }, + }, + ExtraAdapterInfo: "alias-extra-info", + GVLVendorID: 43, + Maintainer: &MaintainerInfo{ + Email: "alias-email@domain.com", + }, + ModifyingVastXmlAllowed: false, + OpenRTB: &OpenRTBInfo{ + GPPSupported: false, + Version: "2.5", + }, + PlatformID: "456", + Syncer: &Syncer{ + Key: "alias", + IFrame: &SyncerEndpoint{ + URL: "https://alias.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://alias-redirect/setuid/iframe", + ExternalURL: "https://alias-iframe.host", + UserMacro: "alias-UID", + }, + }, + XAPI: AdapterXAPI{ + Username: "alias-uname", + Password: "alias-pwd", + Tracker: "alias-tracker", + }, + } + bidderB := parentWithSyncerKey + bidderB.AliasOf = "bidderA" + bidderB.Syncer = &Syncer{ + Key: bidderB.Syncer.Key, + } + + parentWithoutSyncerKey := BidderInfo{ + Syncer: &Syncer{ + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://redirect/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "UID", + }, + }, + } + + bidderC := parentWithoutSyncerKey + bidderC.AliasOf = "bidderA" + bidderC.Syncer = &Syncer{ + Key: "bidderA", + } + + testCases := []struct { + description string + aliasInfos map[string]aliasNillableFields + bidderInfos BidderInfos + expectedBidderInfos BidderInfos + expectedErr error + }{ + { + description: "inherit all parent info in alias bidder, use parent syncer key as syncer alias key", + aliasInfos: map[string]aliasNillableFields{ + "bidderB": { + Disabled: nil, + ModifyingVastXmlAllowed: nil, + Experiment: nil, + XAPI: nil, + }, + }, + bidderInfos: BidderInfos{ + "bidderA": parentWithSyncerKey, + "bidderB": BidderInfo{ + AliasOf: "bidderA", + // all other fields should be inherited from parent bidder + }, + }, + expectedErr: nil, + expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerKey, "bidderB": bidderB}, + }, + { + description: "inherit all parent info in alias bidder, use parent name as syncer alias key", + aliasInfos: map[string]aliasNillableFields{ + "bidderC": { + Disabled: nil, + ModifyingVastXmlAllowed: nil, + Experiment: nil, + XAPI: nil, + }, + }, + bidderInfos: BidderInfos{ + "bidderA": parentWithoutSyncerKey, + "bidderC": BidderInfo{ + AliasOf: "bidderA", + // all other fields should be inherited from parent bidder + }, + }, + expectedErr: nil, + expectedBidderInfos: BidderInfos{"bidderA": parentWithoutSyncerKey, "bidderC": bidderC}, + }, + { + description: "all bidder info specified for alias, do not inherit from parent bidder", + aliasInfos: map[string]aliasNillableFields{ + "bidderB": { + Disabled: &aliasBidderInfo.Disabled, + ModifyingVastXmlAllowed: &aliasBidderInfo.ModifyingVastXmlAllowed, + Experiment: &aliasBidderInfo.Experiment, + XAPI: &aliasBidderInfo.XAPI, + }, + }, + bidderInfos: BidderInfos{ + "bidderA": parentWithSyncerKey, + "bidderB": aliasBidderInfo, + }, + expectedErr: nil, + expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerKey, "bidderB": aliasBidderInfo}, + }, + { + description: "invalid alias", + aliasInfos: map[string]aliasNillableFields{ + "bidderB": {}, + }, + bidderInfos: BidderInfos{ + "bidderB": BidderInfo{ + AliasOf: "bidderA", + }, + }, + expectedErr: errors.New("bidder: bidderA not found for an alias: bidderB"), + }, + { + description: "bidder info not found for an alias", + aliasInfos: map[string]aliasNillableFields{ + "bidderB": {}, + }, + expectedErr: errors.New("bidder info not found for an alias: bidderB"), + }, } + for _, test := range testCases { + bidderInfos, err := processBidderAliases(test.aliasInfos, test.bidderInfos) + if test.expectedErr != nil { + assert.Equal(t, test.expectedErr, err) + } else { + assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description) + } + } } type StubInfoReader struct { @@ -224,7 +575,7 @@ func TestBidderInfoValidationPositive(t *testing.T) { Endpoint: "http://bidderB.com/openrtb2", PlatformID: "B", Maintainer: &MaintainerInfo{ - Email: "maintainer@bidderA.com", + Email: "maintainer@bidderB.com", }, GVLVendorID: 2, Capabilities: &CapabilitiesInfo{ @@ -242,6 +593,43 @@ func TestBidderInfoValidationPositive(t *testing.T) { URL: "http://bidderB.com/usersync", UserMacro: "UID", }, + FormatOverride: SyncResponseFormatRedirect, + }, + }, + "bidderC": BidderInfo{ + Endpoint: "http://bidderB.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, + openrtb_ext.BidTypeBanner, + }, + }, + }, + AliasOf: "bidderB", + }, + "bidderD": BidderInfo{ + Endpoint: "http://bidderD.com/openrtb2", + PlatformID: "D", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderD.com", + }, + GVLVendorID: 3, + Capabilities: &CapabilitiesInfo{ + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, + openrtb_ext.BidTypeBanner, + }, + }, + }, + Syncer: &Syncer{ + FormatOverride: SyncResponseFormatIFrame, }, }, } @@ -249,6 +637,57 @@ func TestBidderInfoValidationPositive(t *testing.T) { assert.Len(t, errs, 0, "All bidder infos should be correct") } +func TestValidateAliases(t *testing.T) { + testCase := struct { + description string + bidderInfos BidderInfos + expectErrors []error + }{ + description: "invalid aliases", + bidderInfos: BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + AliasOf: "bidderB", + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + AliasOf: "bidderC", + }, + }, + expectErrors: []error{ + errors.New("bidder: bidderB cannot be an alias of an alias: bidderA"), + errors.New("bidder: bidderC not found for an alias: bidderB"), + }, + } + + var errs []error + for bidderName, bidderInfo := range testCase.bidderInfos { + errs = append(errs, validateAliases(bidderInfo, testCase.bidderInfos, bidderName)) + } + + assert.ElementsMatch(t, errs, testCase.expectErrors) +} + func TestBidderInfoValidationNegative(t *testing.T) { testCases := []struct { description string @@ -264,23 +703,317 @@ func TestBidderInfoValidationNegative(t *testing.T) { Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + }, + }, + { + "One bidder empty url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("There's no default endpoint available for bidderA. Calls to this bidder/exchange will fail. Please set adapters.bidderA.endpoint in your app config"), + }, + }, + { + "One bidder incorrect url template", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2/getuid?{{.incorrect}}", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("Unable to resolve endpoint: http://bidderA.com/openrtb2/getuid?{{.incorrect}} for adapter: bidderA. template: endpointTemplate:1:37: executing \"endpointTemplate\" at <.incorrect>: can't evaluate field incorrect in type macros.EndpointTemplateParams"), + }, + }, + { + "One bidder incorrect url template parameters", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}}", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("Invalid endpoint template: http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}} for adapter: bidderA. template: endpointTemplate:1: bad character U+005D ']'"), + }, + }, + { + "One bidder no maintainer", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("missing required field: maintainer.email for adapter: bidderA"), + }, + }, + { + "One bidder missing maintainer email", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("missing required field: maintainer.email for adapter: bidderA"), + }, + }, + { + "One bidder missing capabilities", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + }, + }, + []error{ + errors.New("missing required field: capabilities for adapter: bidderA"), + }, + }, + { + "One bidder missing capabilities site and app and dooh", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{}, + }, + }, + []error{ + errors.New("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: bidderA"), + }, + }, + { + "One bidder incorrect capabilities for app", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + "incorrect", + }, + }, + }, + }, + }, + []error{ + errors.New("capabilities.app failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), + }, + }, + { + "One bidder incorrect capabilities for dooh", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + "incorrect", + }, + }, + }, + }, + }, + []error{ + errors.New("capabilities.dooh failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), + }, + }, + { + "One bidder nil capabilities", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: nil, + }, + }, + []error{ + errors.New("missing required field: capabilities for adapter: bidderA"), + }, + }, + { + "One bidder invalid syncer", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + Syncer: &Syncer{ + Supports: []string{"incorrect"}, + }, + }, + }, + []error{ + errors.New("syncer could not be created, invalid supported endpoint: incorrect"), + }, + }, + { + "Two bidders, one with incorrect url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderB.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderB.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + }, + }, + { + "Two bidders, both with incorrect url", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + "bidderB": BidderInfo{ + Endpoint: "incorrect", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderB.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, + }, + }, + []error{ + errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + errors.New("The endpoint: incorrect for bidderB is not a valid URL"), + }, + }, + { + "Invalid alias Site capabilities", + BidderInfos{ + "bidderA": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ openrtb_ext.BidTypeVideo, }, }, }, }, - }, - []error{ - errors.New("The endpoint: incorrect for bidderA is not a valid URL"), - }, - }, - { - "One bidder empty url", - BidderInfos{ - "bidderA": BidderInfo{ - Endpoint: "", + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, @@ -291,17 +1024,18 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("There's no default endpoint available for bidderA. Calls to this bidder/exchange will fail. Please set adapters.bidderA.endpoint in your app config"), + errors.New("capabilities for alias: bidderB should be a subset of capabilities for parent bidder: bidderA"), }, }, { - "One bidder incorrect url template", + "Invalid alias App capabilities", BidderInfos{ "bidderA": BidderInfo{ - Endpoint: "http://bidderA.com/openrtb2/getuid?{{.incorrect}}", + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, @@ -313,37 +1047,40 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, }, - }, - []error{ - errors.New("Unable to resolve endpoint: http://bidderA.com/openrtb2/getuid?{{.incorrect}} for adapter: bidderA. template: endpointTemplate:1:37: executing \"endpointTemplate\" at <.incorrect>: can't evaluate field incorrect in type macros.EndpointTemplateParams"), - }, - }, - { - "One bidder incorrect url template parameters", - BidderInfos{ - "bidderA": BidderInfo{ - Endpoint: "http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}}", + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ + Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ openrtb_ext.BidTypeVideo, }, }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("Invalid endpoint template: http://bidderA.com/openrtb2/getuid?r=[{{.]RedirectURL}} for adapter: bidderA. template: endpointTemplate:1: bad character U+005D ']'"), + errors.New("capabilities for alias: bidderB should be a subset of capabilities for parent bidder: bidderA"), }, }, { - "One bidder no maintainer", + "Invalid alias capabilities", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{}, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ @@ -351,66 +1088,67 @@ func TestBidderInfoValidationNegative(t *testing.T) { }, }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("missing required field: maintainer.email for adapter: bidderA"), + errors.New("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: bidderA"), + errors.New("capabilities for alias: bidderB should be a subset of capabilities for parent bidder: bidderA"), }, }, { - "One bidder missing maintainer email", + "Invalid alias MediaTypes for site", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ - Email: "", + Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ + Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ openrtb_ext.BidTypeVideo, }, }, }, }, - }, - []error{ - errors.New("missing required field: maintainer.email for adapter: bidderA"), - }, - }, - { - "One bidder missing capabilities", - BidderInfos{ - "bidderA": BidderInfo{ + "bidderB": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, + Capabilities: &CapabilitiesInfo{ + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + }, + AliasOf: "bidderA", }, }, []error{ - errors.New("missing required field: capabilities for adapter: bidderA"), + errors.New("mediaTypes for alias: bidderB should be a subset of MediaTypes for parent bidder: bidderA"), }, }, { - "One bidder missing capabilities site and app", + "Invalid alias MediaTypes for app", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, - Capabilities: &CapabilitiesInfo{}, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeVideo, + }, + }, + }, }, - }, - []error{ - errors.New("at least one of capabilities.site or capabilities.app must exist for adapter: bidderA"), - }, - }, - { - "One bidder incorrect capabilities for app", - BidderInfos{ - "bidderA": BidderInfo{ + "bidderB": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", @@ -418,33 +1156,49 @@ func TestBidderInfoValidationNegative(t *testing.T) { Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - "incorrect", + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, }, }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("capabilities.app failed validation: unrecognized media type at index 0: incorrect for adapter: bidderA"), + errors.New("mediaTypes for alias: bidderB should be a subset of MediaTypes for parent bidder: bidderA"), }, }, { - "One bidder nil capabilities", + "Invalid parent bidder capabilities", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, - Capabilities: nil, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + }, + }, + }, + AliasOf: "bidderA", }, }, []error{ errors.New("missing required field: capabilities for adapter: bidderA"), + errors.New("capabilities for alias: bidderB should be a subset of capabilities for parent bidder: bidderA"), }, }, { - "One bidder invalid syncer", + "Invalid site alias capabilities with both site and app", BidderInfos{ "bidderA": BidderInfo{ Endpoint: "http://bidderA.com/openrtb2", @@ -452,88 +1206,151 @@ func TestBidderInfoValidationNegative(t *testing.T) { Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeNative, }, }, }, - Syncer: &Syncer{ - Supports: []string{"incorrect"}, + }, + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("syncer could not be created, invalid supported endpoint: incorrect"), + errors.New("mediaTypes for alias: bidderB should be a subset of MediaTypes for parent bidder: bidderA"), }, }, { - "Two bidders, one with incorrect url", + "Invalid app alias capabilities with both site and app", BidderInfos{ "bidderA": BidderInfo{ - Endpoint: "incorrect", + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeNative, }, }, }, }, "bidderB": BidderInfo{ - Endpoint: "http://bidderB.com/openrtb2", + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ - Email: "maintainer@bidderB.com", + Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, }, }, }, + AliasOf: "bidderA", }, }, []error{ - errors.New("The endpoint: incorrect for bidderA is not a valid URL"), + errors.New("mediaTypes for alias: bidderB should be a subset of MediaTypes for parent bidder: bidderA"), }, }, { - "Two bidders, both with incorrect url", + "Invalid parent bidder for alias", BidderInfos{ - "bidderA": BidderInfo{ - Endpoint: "incorrect", + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, }, }, }, + AliasOf: "bidderC", }, + }, + []error{ + errors.New("parent bidder: bidderC not found for an alias: bidderB"), + }, + }, + { + "Invalid format override value", + BidderInfos{ "bidderB": BidderInfo{ - Endpoint: "incorrect", + Endpoint: "http://bidderA.com/openrtb2", Maintainer: &MaintainerInfo{ - Email: "maintainer@bidderB.com", + Email: "maintainer@bidderA.com", }, Capabilities: &CapabilitiesInfo{ App: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{ - openrtb_ext.BidTypeVideo, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, }, }, }, + Syncer: &Syncer{ + FormatOverride: "x", + }, }, }, []error{ - errors.New("The endpoint: incorrect for bidderA is not a valid URL"), - errors.New("The endpoint: incorrect for bidderB is not a valid URL"), + errors.New("syncer could not be created, invalid format override value: x"), }, }, } @@ -706,122 +1523,65 @@ func TestSyncerEndpointOverride(t *testing.T) { } func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { - var testCases = []struct { - description string - givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos - expectedError string - expectedBidderInfos BidderInfos - }{ - { - description: "Syncer Override", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, - expectedBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, - }, - { - description: "UserSyncURL Override IFrame", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "original"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Supports IFrame", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"iframe"}, IFrame: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Override Redirect", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"redirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Supports: []string{"redirect"}, Redirect: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Supports Redirect", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "original"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedBidderInfos: BidderInfos{"a": {UserSyncURL: "override", Syncer: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}}}, - }, - { - description: "UserSyncURL Override Syncer Not Defined", - givenFsBidderInfos: BidderInfos{"a": {}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define a user sync", - }, - { - description: "UserSyncURL Override Syncer Endpoints Not Defined", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", - }, - { - description: "UserSyncURL Override Ambiguous", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{IFrame: &SyncerEndpoint{URL: "originalIFrame"}, Redirect: &SyncerEndpoint{URL: "originalRedirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", - }, - { - description: "UserSyncURL Supports Ambiguous", - givenFsBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Supports: []string{"iframe", "redirect"}}}}, - givenConfigBidderInfos: BidderInfos{"a": {UserSyncURL: "override"}}, - expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", - }, - } - - for _, test := range testCases { - bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) - if test.expectedError == "" { - assert.NoError(t, resultErr, test.description+":err") - assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result") - } else { - assert.EqualError(t, resultErr, test.expectedError, test.description+":err") + var ( + givenFileSystem = BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}} + givenConfig = nillableFieldBidderInfos{ + "a": { + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }, } - } + expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} + ) + + result, resultErr := applyBidderInfoConfigOverrides(givenConfig, givenFileSystem, mockNormalizeBidderName) + assert.NoError(t, resultErr) + assert.Equal(t, expected, result) } func TestApplyBidderInfoConfigOverrides(t *testing.T) { + falseValue := false + var testCases = []struct { description string givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos + givenConfigBidderInfos nillableFieldBidderInfos expectedError string expectedBidderInfos BidderInfos }{ { description: "Don't override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override Maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, }, { @@ -829,7 +1589,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, @@ -840,10 +1600,10 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, - }}, + }}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, @@ -852,25 +1612,25 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {GVLVendorID: 5}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {}}, - givenConfigBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { @@ -878,7 +1638,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -887,9 +1647,9 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { description: "Override XAPI", givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username", Password: "password", Tracker: "tracker"}}}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, - Syncer: &Syncer{Key: "override"}}}, + Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -897,39 +1657,93 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID1"}}, - givenConfigBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret1"}}, - givenConfigBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, }, + { + description: "Don't override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: nil}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: false, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: nil}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override AliasOf", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Attempt override AliasOf but ignored", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AliasOf: "Alias2"}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Two bidder infos: One with overrides and one without", + givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}, "b": {Endpoint: "b endpoint"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}, "b": {Endpoint: "b endpoint"}}, + }, } for _, test := range testCases { bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) @@ -940,26 +1754,31 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { var testCases = []struct { - description string - givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos - expectedError string - expectedBidderInfos BidderInfos + description string + givenFsBidderInfos BidderInfos + givenNillableFieldBidderInfos nillableFieldBidderInfos + expectedError string + expectedBidderInfos BidderInfos }{ { - description: "Bidder doesn't exists in bidder list", - givenConfigBidderInfos: BidderInfos{"unknown": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error setting configuration for bidder unknown: unknown bidder", + description: "Bidder doesn't exists in bidder list", + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"unknown": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error setting configuration for bidder unknown: unknown bidder", }, { - description: "Bidder doesn't exists in file system", - givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"bidderA": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error finding configuration for bidder bidderA: unknown bidder", + description: "Bidder doesn't exists in file system", + givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"bidderA": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error finding configuration for bidder bidderA: unknown bidder", }, } for _, test := range testCases { - _, err := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + + _, err := applyBidderInfoConfigOverrides(test.givenNillableFieldBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) assert.ErrorContains(t, err, test.expectedError, test.description+":err") } } @@ -967,14 +1786,27 @@ func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { func TestReadFullYamlBidderConfig(t *testing.T) { bidder := "bidderA" bidderInf := BidderInfo{} + err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf) - actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + require.NoError(t, err) - assert.NoError(t, err, "Error wasn't expected") + bidderInfoOverrides := nillableFieldBidderInfos{ + bidder: nillableFieldBidderInfo{ + bidderInfo: bidderInf, + nillableFields: bidderInfoNillableFields{ + Disabled: &bidderInf.Disabled, + ModifyingVastXmlAllowed: &bidderInf.ModifyingVastXmlAllowed, + }, + }, + } + bidderInfoBase := BidderInfos{ + bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}, + } + actualBidderInfo, err := applyBidderInfoConfigOverrides(bidderInfoOverrides, bidderInfoBase, mockNormalizeBidderName) + require.NoError(t, err) expectedBidderInfo := BidderInfos{ bidder: { - Disabled: false, Maintainer: &MaintainerInfo{ Email: "some-email@domain.com", }, @@ -986,18 +1818,44 @@ func TestReadFullYamlBidderConfig(t *testing.T) { Site: &PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, + DOOH: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + }, }, - Debug: &DebugInfo{Allow: true}, ModifyingVastXmlAllowed: true, - Syncer: &Syncer{ - Supports: []string{"iframe"}, + Debug: &DebugInfo{ + Allow: true, + }, + Experiment: BidderInfoExperiment{ + AdsCert: BidderAdsCert{ + Enabled: true, + }, }, - Experiment: BidderInfoExperiment{AdsCert: BidderAdsCert{Enabled: true}}, EndpointCompression: "GZIP", OpenRTB: &OpenRTBInfo{ - Version: "2.6", GPPSupported: true, + Version: "2.6", + }, + Disabled: false, + ExtraAdapterInfo: "extra-info", + AppSecret: "app-secret", + PlatformID: "123", + Syncer: &Syncer{ + Key: "foo", + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "https://redirect/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "UID", + }, + Supports: []string{"iframe"}, + }, + XAPI: AdapterXAPI{ + Username: "uname", + Password: "pwd", + Tracker: "tracker", }, + Endpoint: "https://endpoint.com", }, } assert.Equalf(t, expectedBidderInfo, actualBidderInfo, "Bidder info objects aren't matching") diff --git a/config/compression.go b/config/compression.go index db85202b4a8..2fe8e7b22ac 100644 --- a/config/compression.go +++ b/config/compression.go @@ -1,6 +1,6 @@ package config -import "github.com/prebid/prebid-server/util/httputil" +import "github.com/prebid/prebid-server/v2/util/httputil" type Compression struct { Request CompressionInfo `mapstructure:"request"` diff --git a/config/compression_test.go b/config/compression_test.go index cd9048cd99e..230d1912345 100644 --- a/config/compression_test.go +++ b/config/compression_test.go @@ -3,7 +3,7 @@ package config import ( "testing" - "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/v2/util/httputil" "github.com/stretchr/testify/assert" ) diff --git a/config/config.go b/config/config.go index ee18c746e88..60a541e2ed0 100644 --- a/config/config.go +++ b/config/config.go @@ -6,18 +6,16 @@ import ( "fmt" "net/url" "reflect" - "strconv" "strings" "time" "github.com/golang/glog" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/spf13/viper" - - "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" ) // Configuration specifies the static application config. @@ -30,7 +28,6 @@ type Configuration struct { Client HTTPClient `mapstructure:"http_client"` CacheClient HTTPClient `mapstructure:"http_client_cache"` AdminPort int `mapstructure:"admin_port"` - EnableGzip bool `mapstructure:"enable_gzip"` Compression Compression `mapstructure:"compression"` // GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but // serves as a hack to trigger the garbage collector only when the heap reaches at least this size. @@ -71,9 +68,6 @@ type Configuration struct { // Array of blacklisted apps that is used to create the hash table BlacklistedAppMap so App.ID's can be instantly accessed. BlacklistedApps []string `mapstructure:"blacklisted_apps,flow"` BlacklistedAppMap map[string]bool - // Array of blacklisted accounts that is used to create the hash table BlacklistedAcctMap so Account.ID's can be instantly accessed. - BlacklistedAccts []string `mapstructure:"blacklisted_accts,flow"` - BlacklistedAcctMap map[string]bool // Is publisher/account ID required to be submitted in the OpenRTB2 request AccountRequired bool `mapstructure:"account_required"` // AccountDefaults defines default settings for valid accounts that are partially defined @@ -109,7 +103,16 @@ type Configuration struct { } type PriceFloors struct { - Enabled bool `mapstructure:"enabled"` + Enabled bool `mapstructure:"enabled"` + Fetcher PriceFloorFetcher `mapstructure:"fetcher"` +} + +type PriceFloorFetcher struct { + HttpClient HTTPClient `mapstructure:"http_client"` + CacheSize int `mapstructure:"cache_size_mb"` + Worker int `mapstructure:"worker"` + Capacity int `mapstructure:"capacity"` + MaxRetries int `mapstructure:"max_retries"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -142,21 +145,15 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`) } - if cfg.PriceFloors.Enabled { - glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`) - } - - if len(cfg.AccountDefaults.Events.VASTEvents) > 0 { - errs = append(errs, errors.New("account_defaults.Events.VASTEvents has no effect as the feature is under development.")) - } - - if cfg.TmaxAdjustments.Enabled { - glog.Warning(`cfg.TmaxAdjustments.Enabled will currently not do anything as tmax adjustment feature is still under development.`) - cfg.TmaxAdjustments.Enabled = false + if cfg.AccountDefaults.Events.Enabled { + glog.Warning(`account_defaults.events has no effect as the feature is under development.`) } errs = cfg.Experiment.validate(errs) errs = cfg.BidderInfos.validate(errs) + errs = cfg.AccountDefaults.Privacy.IPv6Config.Validate(errs) + errs = cfg.AccountDefaults.Privacy.IPv4Config.Validate(errs) + return errs } @@ -414,7 +411,6 @@ func (t *TCF2) PurposeOneTreatmentAccessAllowed() bool { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead EnforceAlgo string `mapstructure:"enforce_algo"` // Integer representation of enforcement algo for performance improvement on compares EnforceAlgoID TCF2EnforcementAlgo @@ -698,9 +694,6 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin // Update account defaults and generate base json for patch c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config - // Update the deprecated and new events enabled values for account defaults. - c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled = migrateConfigEventsEnabled(c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled) - if err := c.MarshalAccountDefaults(); err != nil { return nil, err } @@ -767,17 +760,14 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin c.BlacklistedAppMap[c.BlacklistedApps[i]] = true } - // To look for a request's account id in O(1) time, we fill this hash table located in the - // the BlacklistedAccts field of the Configuration struct defined in this file - c.BlacklistedAcctMap = make(map[string]bool) - for i := 0; i < len(c.BlacklistedAccts); i++ { - c.BlacklistedAcctMap[c.BlacklistedAccts[i]] = true - } - // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. resolvedStoredRequestsConfig(&c) - mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos, normalizeBidderName) + configBidderInfosWithNillableFields, err := setConfigBidderInfoNillableFields(v, c.BidderInfos) + if err != nil { + return nil, err + } + mergedBidderInfos, err := applyBidderInfoConfigOverrides(configBidderInfosWithNillableFields, bidderInfos, normalizeBidderName) if err != nil { return nil, err } @@ -792,10 +782,40 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin return &c, nil } +type bidderInfoNillableFields struct { + Disabled *bool `yaml:"disabled" mapstructure:"disabled"` + ModifyingVastXmlAllowed *bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` +} +type nillableFieldBidderInfos map[string]nillableFieldBidderInfo +type nillableFieldBidderInfo struct { + nillableFields bidderInfoNillableFields + bidderInfo BidderInfo +} + +func setConfigBidderInfoNillableFields(v *viper.Viper, bidderInfos BidderInfos) (nillableFieldBidderInfos, error) { + if len(bidderInfos) == 0 || v == nil { + return nil, nil + } + infos := make(nillableFieldBidderInfos, len(bidderInfos)) + + for bidderName, bidderInfo := range bidderInfos { + info := nillableFieldBidderInfo{bidderInfo: bidderInfo} + + if err := v.UnmarshalKey("adapters."+bidderName+".disabled", &info.nillableFields.Disabled); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config disabled: %v", err) + } + if err := v.UnmarshalKey("adapters."+bidderName+".modifyingvastxmlallowed", &info.nillableFields.ModifyingVastXmlAllowed); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config modifyingvastxmlallowed: %v", err) + } + infos[bidderName] = info + } + return infos, nil +} + // MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch func (cfg *Configuration) MarshalAccountDefaults() error { var err error - if cfg.accountDefaultsJSON, err = json.Marshal(cfg.AccountDefaults); err != nil { + if cfg.accountDefaultsJSON, err = jsonutil.Marshal(cfg.AccountDefaults); err != nil { glog.Warningf("converting %+v to json: %v", cfg.AccountDefaults, err) } return err @@ -897,6 +917,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") + v.SetDefault("stored_requests.database.connection.driver", "") + v.SetDefault("stored_requests.database.connection.dbname", "") + v.SetDefault("stored_requests.database.connection.host", "") + v.SetDefault("stored_requests.database.connection.port", 0) + v.SetDefault("stored_requests.database.connection.user", "") + v.SetDefault("stored_requests.database.connection.password", "") + v.SetDefault("stored_requests.database.connection.query_string", "") + v.SetDefault("stored_requests.database.connection.tls.root_cert", "") + v.SetDefault("stored_requests.database.connection.tls.client_cert", "") + v.SetDefault("stored_requests.database.connection.tls.client_key", "") + v.SetDefault("stored_requests.database.fetcher.query", "") + v.SetDefault("stored_requests.database.fetcher.amp_query", "") + v.SetDefault("stored_requests.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_requests.database.initialize_caches.query", "") + v.SetDefault("stored_requests.database.initialize_caches.amp_query", "") + v.SetDefault("stored_requests.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_requests.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_requests.database.poll_for_updates.query", "") + v.SetDefault("stored_requests.database.poll_for_updates.amp_query", "") v.SetDefault("stored_requests.filesystem.enabled", false) v.SetDefault("stored_requests.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("stored_requests.directorypath", "./stored_requests/data/by_id") @@ -914,6 +953,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("stored_requests.http_events.timeout_ms", 0) // stored_video is short for stored_video_requests. // PBS is not in the business of storing video content beyond the normal prebid cache system. + v.SetDefault("stored_video_req.database.connection.driver", "") + v.SetDefault("stored_video_req.database.connection.dbname", "") + v.SetDefault("stored_video_req.database.connection.host", "") + v.SetDefault("stored_video_req.database.connection.port", 0) + v.SetDefault("stored_video_req.database.connection.user", "") + v.SetDefault("stored_video_req.database.connection.password", "") + v.SetDefault("stored_video_req.database.connection.query_string", "") + v.SetDefault("stored_video_req.database.connection.tls.root_cert", "") + v.SetDefault("stored_video_req.database.connection.tls.client_cert", "") + v.SetDefault("stored_video_req.database.connection.tls.client_key", "") + v.SetDefault("stored_video_req.database.fetcher.query", "") + v.SetDefault("stored_video_req.database.fetcher.amp_query", "") + v.SetDefault("stored_video_req.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_video_req.database.initialize_caches.query", "") + v.SetDefault("stored_video_req.database.initialize_caches.amp_query", "") + v.SetDefault("stored_video_req.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_video_req.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_video_req.database.poll_for_updates.query", "") + v.SetDefault("stored_video_req.database.poll_for_updates.amp_query", "") v.SetDefault("stored_video_req.filesystem.enabled", false) v.SetDefault("stored_video_req.filesystem.directorypath", "") v.SetDefault("stored_video_req.http.endpoint", "") @@ -927,6 +985,25 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("stored_video_req.http_events.endpoint", "") v.SetDefault("stored_video_req.http_events.refresh_rate_seconds", 0) v.SetDefault("stored_video_req.http_events.timeout_ms", 0) + v.SetDefault("stored_responses.database.connection.driver", "") + v.SetDefault("stored_responses.database.connection.dbname", "") + v.SetDefault("stored_responses.database.connection.host", "") + v.SetDefault("stored_responses.database.connection.port", 0) + v.SetDefault("stored_responses.database.connection.user", "") + v.SetDefault("stored_responses.database.connection.password", "") + v.SetDefault("stored_responses.database.connection.query_string", "") + v.SetDefault("stored_responses.database.connection.tls.root_cert", "") + v.SetDefault("stored_responses.database.connection.tls.client_cert", "") + v.SetDefault("stored_responses.database.connection.tls.client_key", "") + v.SetDefault("stored_responses.database.fetcher.query", "") + v.SetDefault("stored_responses.database.fetcher.amp_query", "") + v.SetDefault("stored_responses.database.initialize_caches.timeout_ms", 0) + v.SetDefault("stored_responses.database.initialize_caches.query", "") + v.SetDefault("stored_responses.database.initialize_caches.amp_query", "") + v.SetDefault("stored_responses.database.poll_for_updates.refresh_rate_seconds", 0) + v.SetDefault("stored_responses.database.poll_for_updates.timeout_ms", 0) + v.SetDefault("stored_responses.database.poll_for_updates.query", "") + v.SetDefault("stored_responses.database.poll_for_updates.amp_query", "") v.SetDefault("stored_responses.filesystem.enabled", false) v.SetDefault("stored_responses.filesystem.directorypath", "") v.SetDefault("stored_responses.http.endpoint", "") @@ -947,6 +1024,8 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("event.timeout_ms", 1000) + v.SetDefault("user_sync.priority_groups", [][]string{}) + v.SetDefault("accounts.filesystem.enabled", false) v.SetDefault("accounts.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("accounts.in_memory_cache.type", "none") @@ -1020,8 +1099,30 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("account_defaults.price_floors.use_dynamic_data", false) v.SetDefault("account_defaults.price_floors.max_rules", 100) v.SetDefault("account_defaults.price_floors.max_schema_dims", 3) + v.SetDefault("account_defaults.price_floors.fetch.enabled", false) + v.SetDefault("account_defaults.price_floors.fetch.url", "") + v.SetDefault("account_defaults.price_floors.fetch.timeout_ms", 3000) + v.SetDefault("account_defaults.price_floors.fetch.max_file_size_kb", 100) + v.SetDefault("account_defaults.price_floors.fetch.max_rules", 1000) + v.SetDefault("account_defaults.price_floors.fetch.max_age_sec", 86400) + v.SetDefault("account_defaults.price_floors.fetch.period_sec", 3600) + v.SetDefault("account_defaults.price_floors.fetch.max_schema_dims", 0) + v.SetDefault("account_defaults.events_enabled", false) + v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56) + v.SetDefault("account_defaults.privacy.ipv4.anon_keep_bits", 24) + + //Defaults for Price floor fetcher + v.SetDefault("price_floors.fetcher.worker", 20) + v.SetDefault("price_floors.fetcher.capacity", 20000) + v.SetDefault("price_floors.fetcher.cache_size_mb", 64) + v.SetDefault("price_floors.fetcher.http_client.max_connections_per_host", 0) // unlimited + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections", 40) + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections_per_host", 2) + v.SetDefault("price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("price_floors.fetcher.max_retries", 10) + v.SetDefault("account_defaults.events_enabled", false) v.SetDefault("compression.response.enable_gzip", false) v.SetDefault("compression.request.enable_gzip", false) @@ -1058,8 +1159,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("request_validation.ipv4_private_networks", []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}) v.SetDefault("request_validation.ipv6_private_networks", []string{"::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "2001:db8::/32"}) - bindDatabaseEnvVars(v) - // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetTypeByDefaultValue(true) @@ -1067,27 +1166,9 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.AutomaticEnv() v.ReadInConfig() - // Migrate config settings to maintain compatibility with old configs - migrateConfig(v) - migrateConfigPurposeOneTreatment(v) - migrateConfigSpecialFeature1(v) - migrateConfigTCF2PurposeFlags(v) - migrateConfigDatabaseConnection(v) - migrateConfigCompression(v) - // These defaults must be set after the migrate functions because those functions look for the presence of these // config fields and there isn't a way to detect presence of a config field using the viper package if a default // is set. Viper IsSet and Get functions consider default values. - v.SetDefault("gdpr.tcf2.purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose2.enabled", true) - v.SetDefault("gdpr.tcf2.purpose3.enabled", true) - v.SetDefault("gdpr.tcf2.purpose4.enabled", true) - v.SetDefault("gdpr.tcf2.purpose5.enabled", true) - v.SetDefault("gdpr.tcf2.purpose6.enabled", true) - v.SetDefault("gdpr.tcf2.purpose7.enabled", true) - v.SetDefault("gdpr.tcf2.purpose8.enabled", true) - v.SetDefault("gdpr.tcf2.purpose9.enabled", true) - v.SetDefault("gdpr.tcf2.purpose10.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enforce_algo", TCF2EnforceAlgoFull) v.SetDefault("gdpr.tcf2.purpose2.enforce_algo", TCF2EnforceAlgoFull) v.SetDefault("gdpr.tcf2.purpose3.enforce_algo", TCF2EnforceAlgoFull) @@ -1114,10 +1195,9 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("gdpr.tcf2.special_feature1.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("price_floors.enabled", false) - v.SetDefault("enable_gzip", false) - // Defaults for account_defaults.events.default_url v.SetDefault("account_defaults.events.default_url", "https://PBS_HOST/event?t=##PBS-EVENTTYPE##&vtype=##PBS-VASTEVENT##&b=##PBS-BIDID##&f=i&a=##PBS-ACCOUNTID##&ts=##PBS-TIMESTAMP##&bidder=##PBS-BIDDER##&int=##PBS-INTEGRATION##&mt=##PBS-MEDIATYPE##&ch=##PBS-CHANNEL##&aid=##PBS-AUCTIONID##&l=##PBS-LINEID##") + v.SetDefault("account_defaults.events.enabled", false) v.SetDefault("experiment.adscert.mode", "off") v.SetDefault("experiment.adscert.inprocess.origin", "") @@ -1134,303 +1214,6 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { } } -func migrateConfig(v *viper.Viper) { - // if stored_requests.filesystem is not a map in conf file as expected from defaults, - // means we have old-style settings; migrate them to new filesystem map to avoid breaking viper - if _, ok := v.Get("stored_requests.filesystem").(map[string]interface{}); !ok { - glog.Warning("stored_requests.filesystem should be changed to stored_requests.filesystem.enabled") - glog.Warning("stored_requests.directorypath should be changed to stored_requests.filesystem.directorypath") - m := v.GetStringMap("stored_requests.filesystem") - m["enabled"] = v.GetBool("stored_requests.filesystem") - m["directorypath"] = v.GetString("stored_requests.directorypath") - v.Set("stored_requests.filesystem", m) - } -} - -func migrateConfigCompression(v *viper.Viper) { - oldField := "enable_gzip" - newField := "compression.response.enable_gzip" - if v.IsSet(oldField) { - oldConfig := v.GetBool(oldField) - if v.IsSet(newField) { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } else { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - v.Set(newField, oldConfig) - } - } -} - -func migrateConfigPurposeOneTreatment(v *viper.Viper) { - if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok { - if v.IsSet("gdpr.tcf2.purpose_one_treatment") { - glog.Warning("using gdpr.tcf2.purpose_one_treatment and ignoring deprecated gdpr.tcf2.purpose_one_treatement") - } else { - glog.Warning("gdpr.tcf2.purpose_one_treatement.enabled should be changed to gdpr.tcf2.purpose_one_treatment.enabled") - glog.Warning("gdpr.tcf2.purpose_one_treatement.access_allowed should be changed to gdpr.tcf2.purpose_one_treatment.access_allowed") - v.Set("gdpr.tcf2.purpose_one_treatment", oldConfig) - } - } -} - -func migrateConfigSpecialFeature1(v *viper.Viper) { - if oldConfig, ok := v.Get("gdpr.tcf2.special_purpose1").(map[string]interface{}); ok { - if v.IsSet("gdpr.tcf2.special_feature1") { - glog.Warning("using gdpr.tcf2.special_feature1 and ignoring deprecated gdpr.tcf2.special_purpose1") - } else { - glog.Warning("gdpr.tcf2.special_purpose1.enabled is deprecated and should be changed to gdpr.tcf2.special_feature1.enforce") - glog.Warning("gdpr.tcf2.special_purpose1.vendor_exceptions is deprecated and should be changed to gdpr.tcf2.special_feature1.vendor_exceptions") - v.Set("gdpr.tcf2.special_feature1.enforce", oldConfig["enabled"]) - v.Set("gdpr.tcf2.special_feature1.vendor_exceptions", oldConfig["vendor_exceptions"]) - } - } -} - -func migrateConfigTCF2PurposeFlags(v *viper.Viper) { - migrateConfigTCF2EnforcePurposeFlags(v) - migrateConfigTCF2PurposeEnabledFlags(v) -} - -func migrateConfigTCF2EnforcePurposeFlags(v *viper.Viper) { - for i := 1; i <= 10; i++ { - algoField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_algo", i) - purposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) - - if !v.IsSet(purposeField) { - continue - } - if _, ok := v.Get(purposeField).(string); !ok { - continue - } - if v.IsSet(algoField) { - glog.Warningf("using %s and ignoring deprecated %s string type", algoField, purposeField) - } else { - v.Set(algoField, TCF2EnforceAlgoFull) - - glog.Warningf("setting %s to \"%s\" based on deprecated %s string type \"%s\"", algoField, TCF2EnforceAlgoFull, purposeField, v.GetString(purposeField)) - } - - oldPurposeFieldValue := v.GetString(purposeField) - newPurposeFieldValue := "false" - if oldPurposeFieldValue == TCF2EnforceAlgoFull { - newPurposeFieldValue = "true" - } - - glog.Warningf("converting %s from string \"%s\" to bool \"%s\"; string type is deprecated", purposeField, oldPurposeFieldValue, newPurposeFieldValue) - v.Set(purposeField, newPurposeFieldValue) - } -} - -func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { - for i := 1; i <= 10; i++ { - oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) - newField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) - - if v.IsSet(oldField) { - oldConfig := v.GetBool(oldField) - if v.IsSet(newField) { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } else { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - v.Set(newField, oldConfig) - } - } - - if v.IsSet(newField) { - v.Set(oldField, strconv.FormatBool(v.GetBool(newField))) - } - } -} - -func migrateConfigDatabaseConnection(v *viper.Viper) { - - type QueryParamMigration struct { - old string - new string - } - - type QueryMigration struct { - name string - params []QueryParamMigration - } - - type Migration struct { - old string - new string - fields []string - queryMigrations []QueryMigration - } - - queryParamMigrations := struct { - RequestIdList QueryParamMigration - ImpIdList QueryParamMigration - IdList QueryParamMigration - LastUpdated QueryParamMigration - }{ - RequestIdList: QueryParamMigration{ - old: "%REQUEST_ID_LIST%", - new: "$REQUEST_ID_LIST", - }, - ImpIdList: QueryParamMigration{ - old: "%IMP_ID_LIST%", - new: "$IMP_ID_LIST", - }, - IdList: QueryParamMigration{ - old: "%ID_LIST%", - new: "$ID_LIST", - }, - LastUpdated: QueryParamMigration{ - old: "$1", - new: "$LAST_UPDATED", - }, - } - - queryMigrations := []QueryMigration{ - { - name: "fetcher.query", - params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, - }, - { - name: "fetcher.amp_query", - params: []QueryParamMigration{queryParamMigrations.RequestIdList, queryParamMigrations.ImpIdList, queryParamMigrations.IdList}, - }, - { - name: "poll_for_updates.query", - params: []QueryParamMigration{queryParamMigrations.LastUpdated}, - }, - { - name: "poll_for_updates.amp_query", - params: []QueryParamMigration{queryParamMigrations.LastUpdated}, - }, - } - - migrations := []Migration{ - { - old: "stored_requests.postgres", - new: "stored_requests.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "fetcher.amp_query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "initialize_caches.amp_query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - "poll_for_updates.amp_query", - }, - queryMigrations: queryMigrations, - }, - { - old: "stored_video_req.postgres", - new: "stored_video_req.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - }, - queryMigrations: queryMigrations, - }, - { - old: "stored_responses.postgres", - new: "stored_responses.database", - fields: []string{ - "connection.dbname", - "connection.host", - "connection.port", - "connection.user", - "connection.password", - "fetcher.query", - "initialize_caches.timeout_ms", - "initialize_caches.query", - "poll_for_updates.refresh_rate_seconds", - "poll_for_updates.timeout_ms", - "poll_for_updates.query", - }, - queryMigrations: queryMigrations, - }, - } - - for _, migration := range migrations { - driverField := migration.new + ".connection.driver" - newConfigInfoPresent := isConfigInfoPresent(v, migration.new, migration.fields) - oldConfigInfoPresent := isConfigInfoPresent(v, migration.old, migration.fields) - - if !newConfigInfoPresent && oldConfigInfoPresent { - glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", migration.old, migration.new)) - glog.Warning(fmt.Sprintf("%s is not set, using default (postgres)", driverField)) - v.Set(driverField, "postgres") - - for _, field := range migration.fields { - oldField := migration.old + "." + field - newField := migration.new + "." + field - if v.IsSet(oldField) { - glog.Warning(fmt.Sprintf("%s is deprecated and should be changed to %s", oldField, newField)) - v.Set(newField, v.Get(oldField)) - } - } - - for _, queryMigration := range migration.queryMigrations { - oldQueryField := migration.old + "." + queryMigration.name - newQueryField := migration.new + "." + queryMigration.name - queryString := v.GetString(oldQueryField) - for _, queryParam := range queryMigration.params { - if strings.Contains(queryString, queryParam.old) { - glog.Warning(fmt.Sprintf("Query param %s for %s is deprecated and should be changed to %s", queryParam.old, oldQueryField, queryParam.new)) - queryString = strings.ReplaceAll(queryString, queryParam.old, queryParam.new) - v.Set(newQueryField, queryString) - } - } - } - } else if newConfigInfoPresent && oldConfigInfoPresent { - glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", migration.new, migration.old)) - - for _, field := range migration.fields { - oldField := migration.old + "." + field - newField := migration.new + "." + field - if v.IsSet(oldField) { - glog.Warning(fmt.Sprintf("using %s and ignoring deprecated %s", newField, oldField)) - } - } - } - } -} - -// migrateConfigEventsEnabled is responsible for ensuring backward compatibility of events_enabled field. -// This function copies the value of newField "events.enabled" and set it to the oldField "events_enabled". -// This is necessary to achieve the desired order of precedence favoring the account values over the host values -// given the account fetcher JSON merge mechanics. -func migrateConfigEventsEnabled(oldFieldValue *bool, newFieldValue *bool) (updatedOldFieldValue, updatedNewFieldValue *bool) { - newField := "account_defaults.events.enabled" - oldField := "account_defaults.events_enabled" - - updatedOldFieldValue = oldFieldValue - if oldFieldValue != nil { - glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) - } - if newFieldValue != nil { - if oldFieldValue != nil { - glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) - } - updatedOldFieldValue = ptrutil.ToPtr(*newFieldValue) - } - - return updatedOldFieldValue, nil -} - func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { prefix = prefix + "." for _, field := range fields { @@ -1442,60 +1225,6 @@ func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool { return false } -func bindDatabaseEnvVars(v *viper.Viper) { - v.BindEnv("stored_requests.database.connection.driver") - v.BindEnv("stored_requests.database.connection.dbname") - v.BindEnv("stored_requests.database.connection.host") - v.BindEnv("stored_requests.database.connection.port") - v.BindEnv("stored_requests.database.connection.user") - v.BindEnv("stored_requests.database.connection.password") - v.BindEnv("stored_requests.database.connection.query_string") - v.BindEnv("stored_requests.database.connection.tls.root_cert") - v.BindEnv("stored_requests.database.connection.tls.client_cert") - v.BindEnv("stored_requests.database.connection.tls.client_key") - v.BindEnv("stored_requests.database.fetcher.query") - v.BindEnv("stored_requests.database.fetcher.amp_query") - v.BindEnv("stored_requests.database.initialize_caches.timeout_ms") - v.BindEnv("stored_requests.database.initialize_caches.query") - v.BindEnv("stored_requests.database.initialize_caches.amp_query") - v.BindEnv("stored_requests.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_requests.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_requests.database.poll_for_updates.query") - v.BindEnv("stored_requests.database.poll_for_updates.amp_query") - v.BindEnv("stored_video_req.database.connection.driver") - v.BindEnv("stored_video_req.database.connection.dbname") - v.BindEnv("stored_video_req.database.connection.host") - v.BindEnv("stored_video_req.database.connection.port") - v.BindEnv("stored_video_req.database.connection.user") - v.BindEnv("stored_video_req.database.connection.password") - v.BindEnv("stored_video_req.database.connection.query_string") - v.BindEnv("stored_video_req.database.connection.tls.root_cert") - v.BindEnv("stored_video_req.database.connection.tls.client_cert") - v.BindEnv("stored_video_req.database.connection.tls.client_key") - v.BindEnv("stored_video_req.database.fetcher.query") - v.BindEnv("stored_video_req.database.initialize_caches.timeout_ms") - v.BindEnv("stored_video_req.database.initialize_caches.query") - v.BindEnv("stored_video_req.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_video_req.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_video_req.database.poll_for_updates.query") - v.BindEnv("stored_responses.database.connection.driver") - v.BindEnv("stored_responses.database.connection.dbname") - v.BindEnv("stored_responses.database.connection.host") - v.BindEnv("stored_responses.database.connection.port") - v.BindEnv("stored_responses.database.connection.user") - v.BindEnv("stored_responses.database.connection.password") - v.BindEnv("stored_responses.database.connection.query_string") - v.BindEnv("stored_responses.database.connection.tls.root_cert") - v.BindEnv("stored_responses.database.connection.tls.client_cert") - v.BindEnv("stored_responses.database.connection.tls.client_key") - v.BindEnv("stored_responses.database.fetcher.query") - v.BindEnv("stored_responses.database.initialize_caches.timeout_ms") - v.BindEnv("stored_responses.database.initialize_caches.query") - v.BindEnv("stored_responses.database.poll_for_updates.refresh_rate_seconds") - v.BindEnv("stored_responses.database.poll_for_updates.timeout_ms") - v.BindEnv("stored_responses.database.poll_for_updates.query") -} - func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." + bidder v.BindEnv(adapterCfgPrefix + ".disabled") diff --git a/config/config_test.go b/config/config_test.go index 402f9ada8db..a551c1be66e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,14 +5,12 @@ import ( "errors" "net" "os" - "strconv" "strings" "testing" "time" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -35,7 +33,6 @@ var bidderInfos = BidderInfos{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, }, }, - UserSyncURL: "http://bidder2.com/usersync", }, } @@ -178,9 +175,16 @@ func TestDefaults(t *testing.T) { //Assert the price floor default values cmpBools(t, "price_floors.enabled", false, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 64, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 0, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 40, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 10, cfg.PriceFloors.Fetcher.MaxRetries) // Assert compression related defaults - cmpBools(t, "enable_gzip", false, cfg.EnableGzip) cmpBools(t, "compression.request.enable_gzip", false, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) @@ -191,8 +195,16 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", false, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 100, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 3, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) - cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, false) - cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", false, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 3000, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 100, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 1000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 3600, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 86400, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 0, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + + cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled) cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled) cmpStrings(t, "validations.banner_creative_max_size", "skip", cfg.Validations.BannerCreativeMaxSize) @@ -206,11 +218,13 @@ func TestDefaults(t *testing.T) { cmpUnsignedInts(t, "tmax_adjustments.bidder_network_latency_buffer_ms", 0, cfg.TmaxAdjustments.BidderNetworkLatencyBuffer) cmpUnsignedInts(t, "tmax_adjustments.pbs_response_preparation_duration_ms", 0, cfg.TmaxAdjustments.PBSResponsePreparationDuration) + cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 56, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) + cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 24, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) + //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -219,7 +233,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose2: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -228,7 +241,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose3: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -237,7 +249,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose4: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -246,7 +257,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose5: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -255,7 +265,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose6: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -264,7 +273,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose7: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -273,7 +281,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose8: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -282,7 +289,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose9: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -291,7 +297,6 @@ func TestDefaults(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose10: TCF2Purpose{ - Enabled: true, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -336,7 +341,6 @@ gdpr: enforce_vendors: false vendor_exceptions: ["foo1a", "foo1b"] purpose2: - enabled: false enforce_algo: "full" enforce_purpose: false enforce_vendors: false @@ -383,7 +387,6 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 -enable_gzip: false compression: request: enable_gzip: true @@ -465,8 +468,17 @@ hooks: enabled: true price_floors: enabled: true + fetcher: + worker: 20 + capacity: 20000 + cache_size_mb: 8 + http_client: + max_connections_per_host: 5 + max_idle_connections: 1 + max_idle_connections_per_host: 2 + idle_connection_timeout_seconds: 10 + max_retries: 5 account_defaults: - events_enabled: false events: enabled: true price_floors: @@ -477,6 +489,20 @@ account_defaults: use_dynamic_data: true max_rules: 120 max_schema_dims: 5 + fetch: + enabled: true + url: http://test.com/floors + timeout_ms: 500 + max_file_size_kb: 200 + max_rules: 500 + period_sec: 2000 + max_age_sec: 6000 + max_schema_dims: 10 + privacy: + ipv6: + anon_keep_bits: 50 + ipv4: + anon_keep_bits: 20 tmax_adjustments: enabled: true bidder_response_duration_min_ms: 700 @@ -484,12 +510,6 @@ tmax_adjustments: pbs_response_preparation_duration_ms: 100 `) -var oldStoredRequestsConfig = []byte(` -stored_requests: - filesystem: true - directorypath: "/somepath" -`) - func cmpStrings(t *testing.T, key, expected, actual string) { t.Helper() assert.Equal(t, expected, actual, "%s: %s != %s", key, expected, actual) @@ -566,13 +586,21 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "validations.secure_markup", "skip", cfg.Validations.SecureMarkup) cmpInts(t, "validations.max_creative_width", 0, int(cfg.Validations.MaxCreativeWidth)) cmpInts(t, "validations.max_creative_height", 0, int(cfg.Validations.MaxCreativeHeight)) - cmpBools(t, "tmax_adjustments.enabled", false, cfg.TmaxAdjustments.Enabled) // Tmax adjustment feature is still under development. Therefore enabled flag is set to false + cmpBools(t, "tmax_adjustments.enabled", true, cfg.TmaxAdjustments.Enabled) cmpUnsignedInts(t, "tmax_adjustments.bidder_response_duration_min_ms", 700, cfg.TmaxAdjustments.BidderResponseDurationMin) cmpUnsignedInts(t, "tmax_adjustments.bidder_network_latency_buffer_ms", 100, cfg.TmaxAdjustments.BidderNetworkLatencyBuffer) cmpUnsignedInts(t, "tmax_adjustments.pbs_response_preparation_duration_ms", 100, cfg.TmaxAdjustments.PBSResponsePreparationDuration) //Assert the price floor values cmpBools(t, "price_floors.enabled", true, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 8, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 5, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 1, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 10, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 5, cfg.PriceFloors.Fetcher.MaxRetries) cmpBools(t, "account_defaults.price_floors.enabled", true, cfg.AccountDefaults.PriceFloors.Enabled) cmpInts(t, "account_defaults.price_floors.enforce_floors_rate", 50, cfg.AccountDefaults.PriceFloors.EnforceFloorsRate) cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", false, cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment) @@ -580,11 +608,21 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", true, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 120, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 5, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) - cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, true) - cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", true, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "http://test.com/floors", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 500, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 200, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 500, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 2000, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 6000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 10, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + + cmpBools(t, "account_defaults.events.enabled", true, cfg.AccountDefaults.Events.Enabled) + + cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) + cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) // Assert compression related defaults - cmpBools(t, "enable_gzip", false, cfg.EnableGzip) cmpBools(t, "compression.request.enable_gzip", true, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) @@ -612,7 +650,6 @@ func TestFullConfig(t *testing.T) { expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -621,7 +658,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, }, Purpose2: TCF2Purpose{ - Enabled: false, EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: false, @@ -630,7 +666,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, }, Purpose3: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoBasic, EnforceAlgoID: TCF2BasicEnforcement, EnforcePurpose: true, @@ -639,7 +674,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, }, Purpose4: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -648,7 +682,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, }, Purpose5: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -657,7 +690,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, }, Purpose6: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -666,7 +698,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, }, Purpose7: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -675,7 +706,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, }, Purpose8: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -684,7 +714,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, }, Purpose9: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -693,7 +722,6 @@ func TestFullConfig(t *testing.T) { VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, }, Purpose10: TCF2Purpose{ - Enabled: true, // true by default EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, @@ -797,6 +825,15 @@ func TestValidateConfig(t *testing.T) { Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{Type: "none"}, }, + AccountDefaults: Account{ + PriceFloors: AccountPriceFloors{ + Fetcher: AccountFloorFetch{ + Timeout: 100, + Period: 300, + MaxAge: 600, + }, + }, + }, } v := viper.New() @@ -807,36 +844,15 @@ func TestValidateConfig(t *testing.T) { assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } -func TestMigrateConfig(t *testing.T) { - v := viper.New() - SetupViper(v, "", bidderInfos) - v.Set("gdpr.default_value", "0") - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) - migrateConfig(v) - cfg, err := New(v, bidderInfos, mockNormalizeBidderName) - assert.NoError(t, err, "Setting up config should work but it doesn't") - cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) - cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path) -} - func TestMigrateConfigFromEnv(t *testing.T) { - if oldval, ok := os.LookupEnv("PBS_STORED_REQUESTS_FILESYSTEM"); ok { - defer os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", oldval) - } else { - defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") - } - if oldval, ok := os.LookupEnv("PBS_ADAPTERS_BIDDER1_ENDPOINT"); ok { defer os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", oldval) } else { defer os.Unsetenv("PBS_ADAPTERS_BIDDER1_ENDPOINT") } - os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") os.Setenv("PBS_ADAPTERS_BIDDER1_ENDPOINT", "http://bidder1_override.com") cfg, _ := newDefaultConfig(t) - cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "adapters.bidder1.endpoint", "http://bidder1_override.com", cfg.BidderInfos["bidder1"].Endpoint) } @@ -885,7 +901,6 @@ func TestUserSyncFromEnv(t *testing.T) { assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.Redirect) assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.SupportCORS) - assert.Nil(t, cfg.BidderInfos["brightroll"].Syncer) } func TestBidderInfoFromEnv(t *testing.T) { @@ -969,1692 +984,6 @@ func TestBidderInfoFromEnv(t *testing.T) { assert.Equal(t, "2.6", cfg.BidderInfos["bidder1"].OpenRTB.Version) } -func TestMigrateConfigPurposeOneTreatment(t *testing.T) { - oldPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatement: - enabled: true - access_allowed: true - `) - newPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatment: - enabled: true - access_allowed: true - `) - oldAndNewPurposeOneTreatmentConfig := []byte(` - gdpr: - tcf2: - purpose_one_treatement: - enabled: false - access_allowed: true - purpose_one_treatment: - enabled: true - access_allowed: false - `) - - tests := []struct { - description string - config []byte - wantPurpose1TreatmentEnabled bool - wantPurpose1TreatmentAccessAllowed bool - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: oldPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: true, - }, - { - description: "New config set, old config not set", - config: newPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: true, - }, - { - description: "New config and old config set", - config: oldAndNewPurposeOneTreatmentConfig, - wantPurpose1TreatmentEnabled: true, - wantPurpose1TreatmentAccessAllowed: false, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigPurposeOneTreatment(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantPurpose1TreatmentEnabled, v.Get("gdpr.tcf2.purpose_one_treatment.enabled").(bool), tt.description) - assert.Equal(t, tt.wantPurpose1TreatmentAccessAllowed, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed").(bool), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed"), tt.description) - } - } -} - -func TestMigrateConfigSpecialFeature1(t *testing.T) { - oldSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_purpose1: - enabled: true - vendor_exceptions: ["appnexus"] - `) - newSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_feature1: - enforce: true - vendor_exceptions: ["appnexus"] - `) - oldAndNewSpecialFeature1Config := []byte(` - gdpr: - tcf2: - special_purpose1: - enabled: false - vendor_exceptions: ["appnexus"] - special_feature1: - enforce: true - vendor_exceptions: ["rubicon"] - `) - - tests := []struct { - description string - config []byte - wantSpecialFeature1Enforce bool - wantSpecialFeature1VendorExceptions []string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: oldSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"appnexus"}, - }, - { - description: "New config set, old config not set", - config: newSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"appnexus"}, - }, - { - description: "New config and old config set", - config: oldAndNewSpecialFeature1Config, - wantSpecialFeature1Enforce: true, - wantSpecialFeature1VendorExceptions: []string{"rubicon"}, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigSpecialFeature1(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantSpecialFeature1Enforce, v.Get("gdpr.tcf2.special_feature1.enforce").(bool), tt.description) - assert.Equal(t, tt.wantSpecialFeature1VendorExceptions, v.GetStringSlice("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.enforce"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) - } - - var c Configuration - err := v.Unmarshal(&c) - assert.NoError(t, err, tt.description) - assert.Equal(t, tt.wantSpecialFeature1Enforce, c.GDPR.TCF2.SpecialFeature1.Enforce, tt.description) - - // convert expected vendor exceptions to type BidderName - expectedVendorExceptions := make([]openrtb_ext.BidderName, 0, 0) - for _, ve := range tt.wantSpecialFeature1VendorExceptions { - expectedVendorExceptions = append(expectedVendorExceptions, openrtb_ext.BidderName(ve)) - } - assert.ElementsMatch(t, expectedVendorExceptions, c.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description) - } -} - -func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { - trueStr := "true" - falseStr := "false" - - tests := []struct { - description string - config []byte - wantPurpose1EnforcePurpose string - wantPurpose2EnforcePurpose string - wantPurpose3EnforcePurpose string - wantPurpose4EnforcePurpose string - wantPurpose5EnforcePurpose string - wantPurpose6EnforcePurpose string - wantPurpose7EnforcePurpose string - wantPurpose8EnforcePurpose string - wantPurpose9EnforcePurpose string - wantPurpose10EnforcePurpose string - wantPurpose1Enabled string - wantPurpose2Enabled string - wantPurpose3Enabled string - wantPurpose4Enabled string - wantPurpose5Enabled string - wantPurpose6Enabled string - wantPurpose7Enabled string - wantPurpose8Enabled string - wantPurpose9Enabled string - wantPurpose10Enabled string - }{ - { - description: "New config and old config flags not set", - config: []byte{}, - }, - { - description: "New config not set, old config set - use old flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - purpose2: - enabled: true - purpose3: - enabled: false - purpose4: - enabled: true - purpose5: - enabled: false - purpose6: - enabled: true - purpose7: - enabled: false - purpose8: - enabled: true - purpose9: - enabled: false - purpose10: - enabled: true - `), - wantPurpose1EnforcePurpose: falseStr, - wantPurpose2EnforcePurpose: trueStr, - wantPurpose3EnforcePurpose: falseStr, - wantPurpose4EnforcePurpose: trueStr, - wantPurpose5EnforcePurpose: falseStr, - wantPurpose6EnforcePurpose: trueStr, - wantPurpose7EnforcePurpose: falseStr, - wantPurpose8EnforcePurpose: trueStr, - wantPurpose9EnforcePurpose: falseStr, - wantPurpose10EnforcePurpose: trueStr, - wantPurpose1Enabled: falseStr, - wantPurpose2Enabled: trueStr, - wantPurpose3Enabled: falseStr, - wantPurpose4Enabled: trueStr, - wantPurpose5Enabled: falseStr, - wantPurpose6Enabled: trueStr, - wantPurpose7Enabled: falseStr, - wantPurpose8Enabled: trueStr, - wantPurpose9Enabled: falseStr, - wantPurpose10Enabled: trueStr, - }, - { - description: "New config flags set, old config flags not set - use new flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: true - purpose2: - enforce_purpose: false - purpose3: - enforce_purpose: true - purpose4: - enforce_purpose: false - purpose5: - enforce_purpose: true - purpose6: - enforce_purpose: false - purpose7: - enforce_purpose: true - purpose8: - enforce_purpose: false - purpose9: - enforce_purpose: true - purpose10: - enforce_purpose: false - `), - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - wantPurpose1Enabled: trueStr, - wantPurpose2Enabled: falseStr, - wantPurpose3Enabled: trueStr, - wantPurpose4Enabled: falseStr, - wantPurpose5Enabled: trueStr, - wantPurpose6Enabled: falseStr, - wantPurpose7Enabled: trueStr, - wantPurpose8Enabled: falseStr, - wantPurpose9Enabled: trueStr, - wantPurpose10Enabled: falseStr, - }, - { - description: "New config flags and old config flags set - use new flags", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - enforce_purpose: true - purpose2: - enabled: false - enforce_purpose: true - purpose3: - enabled: false - enforce_purpose: true - purpose4: - enabled: false - enforce_purpose: true - purpose5: - enabled: false - enforce_purpose: true - purpose6: - enabled: false - enforce_purpose: true - purpose7: - enabled: false - enforce_purpose: true - purpose8: - enabled: false - enforce_purpose: true - purpose9: - enabled: false - enforce_purpose: true - purpose10: - enabled: false - enforce_purpose: true - `), - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: trueStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: trueStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: trueStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: trueStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: trueStr, - wantPurpose1Enabled: trueStr, - wantPurpose2Enabled: trueStr, - wantPurpose3Enabled: trueStr, - wantPurpose4Enabled: trueStr, - wantPurpose5Enabled: trueStr, - wantPurpose6Enabled: trueStr, - wantPurpose7Enabled: trueStr, - wantPurpose8Enabled: trueStr, - wantPurpose9Enabled: trueStr, - wantPurpose10Enabled: trueStr, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2PurposeEnabledFlags(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose1Enabled, v.GetString("gdpr.tcf2.purpose1.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose2Enabled, v.GetString("gdpr.tcf2.purpose2.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose3Enabled, v.GetString("gdpr.tcf2.purpose3.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose4Enabled, v.GetString("gdpr.tcf2.purpose4.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose5Enabled, v.GetString("gdpr.tcf2.purpose5.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose6Enabled, v.GetString("gdpr.tcf2.purpose6.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose7Enabled, v.GetString("gdpr.tcf2.purpose7.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose8Enabled, v.GetString("gdpr.tcf2.purpose8.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose9Enabled, v.GetString("gdpr.tcf2.purpose9.enabled"), tt.description) - assert.Equal(t, tt.wantPurpose10Enabled, v.GetString("gdpr.tcf2.purpose10.enabled"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enabled"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enabled"), tt.description) - } - } -} - -func TestMigrateConfigTCF2PurposeFlags(t *testing.T) { - tests := []struct { - description string - config []byte - wantPurpose1EnforceAlgo string - wantPurpose1EnforcePurpose bool - wantPurpose1Enabled bool - }{ - { - description: "enforce_purpose does not set enforce_algo but sets enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "off" - enforce_purpose: "full" - enabled: false - purpose2: - enforce_purpose: "full" - enabled: false - purpose3: - enabled: false - `), - wantPurpose1EnforceAlgo: "off", - wantPurpose1EnforcePurpose: true, - wantPurpose1Enabled: true, - }, - { - description: "enforce_purpose sets enforce_algo and enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: "full" - enabled: false - `), - wantPurpose1EnforceAlgo: "full", - wantPurpose1EnforcePurpose: true, - wantPurpose1Enabled: true, - }, - { - description: "enforce_purpose does not set enforce_algo or enabled", - config: []byte(` - gdpr: - tcf2: - purpose1: - enabled: false - `), - wantPurpose1EnforceAlgo: "", - wantPurpose1EnforcePurpose: false, - wantPurpose1Enabled: false, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2PurposeFlags(v) - - assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetBool("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose1Enabled, v.GetBool("gdpr.tcf2.purpose1.enabled"), tt.description) - } - -} - -func TestMigrateConfigTCF2EnforcePurposeFlags(t *testing.T) { - trueStr := "true" - falseStr := "false" - - tests := []struct { - description string - config []byte - wantEnforceAlgosSet bool - wantPurpose1EnforceAlgo string - wantPurpose2EnforceAlgo string - wantPurpose3EnforceAlgo string - wantPurpose4EnforceAlgo string - wantPurpose5EnforceAlgo string - wantPurpose6EnforceAlgo string - wantPurpose7EnforceAlgo string - wantPurpose8EnforceAlgo string - wantPurpose9EnforceAlgo string - wantPurpose10EnforceAlgo string - wantEnforcePurposesSet bool - wantPurpose1EnforcePurpose string - wantPurpose2EnforcePurpose string - wantPurpose3EnforcePurpose string - wantPurpose4EnforcePurpose string - wantPurpose5EnforcePurpose string - wantPurpose6EnforcePurpose string - wantPurpose7EnforcePurpose string - wantPurpose8EnforcePurpose string - wantPurpose9EnforcePurpose string - wantPurpose10EnforcePurpose string - }{ - { - description: "enforce_algo and enforce_purpose are not set", - config: []byte{}, - wantEnforceAlgosSet: false, - wantEnforcePurposesSet: false, - }, - { - description: "enforce_algo not set; set it based on enforce_purpose string value", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: "full" - purpose2: - enforce_purpose: "no" - purpose3: - enforce_purpose: "full" - purpose4: - enforce_purpose: "no" - purpose5: - enforce_purpose: "full" - purpose6: - enforce_purpose: "no" - purpose7: - enforce_purpose: "full" - purpose8: - enforce_purpose: "no" - purpose9: - enforce_purpose: "full" - purpose10: - enforce_purpose: "no" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - }, - { - description: "enforce_algo not set; don't set it based on enforce_purpose bool value", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_purpose: true - purpose2: - enforce_purpose: false - purpose3: - enforce_purpose: true - purpose4: - enforce_purpose: false - purpose5: - enforce_purpose: true - purpose6: - enforce_purpose: false - purpose7: - enforce_purpose: true - purpose8: - enforce_purpose: false - purpose9: - enforce_purpose: true - purpose10: - enforce_purpose: false - `), - wantEnforceAlgosSet: false, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: trueStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: trueStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: trueStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: trueStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: trueStr, - wantPurpose10EnforcePurpose: falseStr, - }, - { - description: "enforce_algo is set and enforce_purpose is not; enforce_algo is unchanged", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "full" - purpose2: - enforce_algo: "full" - purpose3: - enforce_algo: "full" - purpose4: - enforce_algo: "full" - purpose5: - enforce_algo: "full" - purpose6: - enforce_algo: "full" - purpose7: - enforce_algo: "full" - purpose8: - enforce_algo: "full" - purpose9: - enforce_algo: "full" - purpose10: - enforce_algo: "full" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: false, - }, - { - description: "enforce_algo and enforce_purpose are set; enforce_algo is unchanged", - config: []byte(` - gdpr: - tcf2: - purpose1: - enforce_algo: "full" - enforce_purpose: "no" - purpose2: - enforce_algo: "full" - enforce_purpose: "no" - purpose3: - enforce_algo: "full" - enforce_purpose: "no" - purpose4: - enforce_algo: "full" - enforce_purpose: "no" - purpose5: - enforce_algo: "full" - enforce_purpose: "no" - purpose6: - enforce_algo: "full" - enforce_purpose: "no" - purpose7: - enforce_algo: "full" - enforce_purpose: "no" - purpose8: - enforce_algo: "full" - enforce_purpose: "no" - purpose9: - enforce_algo: "full" - enforce_purpose: "no" - purpose10: - enforce_algo: "full" - enforce_purpose: "no" - `), - wantEnforceAlgosSet: true, - wantPurpose1EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose2EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose3EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose4EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose5EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose6EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose7EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose8EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose9EnforceAlgo: TCF2EnforceAlgoFull, - wantPurpose10EnforceAlgo: TCF2EnforceAlgoFull, - wantEnforcePurposesSet: true, - wantPurpose1EnforcePurpose: falseStr, - wantPurpose2EnforcePurpose: falseStr, - wantPurpose3EnforcePurpose: falseStr, - wantPurpose4EnforcePurpose: falseStr, - wantPurpose5EnforcePurpose: falseStr, - wantPurpose6EnforcePurpose: falseStr, - wantPurpose7EnforcePurpose: falseStr, - wantPurpose8EnforcePurpose: falseStr, - wantPurpose9EnforcePurpose: falseStr, - wantPurpose10EnforcePurpose: falseStr, - }, - } - - for _, tt := range tests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigTCF2EnforcePurposeFlags(v) - - if tt.wantEnforceAlgosSet { - assert.Equal(t, tt.wantPurpose1EnforceAlgo, v.GetString("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforceAlgo, v.GetString("gdpr.tcf2.purpose2.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforceAlgo, v.GetString("gdpr.tcf2.purpose3.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforceAlgo, v.GetString("gdpr.tcf2.purpose4.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforceAlgo, v.GetString("gdpr.tcf2.purpose5.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforceAlgo, v.GetString("gdpr.tcf2.purpose6.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforceAlgo, v.GetString("gdpr.tcf2.purpose7.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforceAlgo, v.GetString("gdpr.tcf2.purpose8.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforceAlgo, v.GetString("gdpr.tcf2.purpose9.enforce_algo"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforceAlgo, v.GetString("gdpr.tcf2.purpose10.enforce_algo"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_algo"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_algo"), tt.description) - } - - if tt.wantEnforcePurposesSet { - assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - } else { - assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) - assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) - } - } -} - -func TestMigrateConfigDatabaseConnection(t *testing.T) { - type configs struct { - old []byte - new []byte - both []byte - } - - // Stored Requests Config Migration - storedReqestsConfigs := configs{ - old: []byte(` - stored_requests: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - amp_query: "old_fetcher_amp_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - amp_query: "old_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - amp_query: "old_poll_for_updates_amp_query" - `), - new: []byte(` - stored_requests: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - amp_query: "new_fetcher_amp_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - amp_query: "new_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - amp_query: "new_poll_for_updates_amp_query" - `), - both: []byte(` - stored_requests: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - amp_query: "old_fetcher_amp_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - amp_query: "old_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - amp_query: "old_poll_for_updates_amp_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - amp_query: "new_fetcher_amp_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - amp_query: "new_initialize_caches_amp_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - amp_query: "new_poll_for_updates_amp_query" - `), - } - - storedRequestsTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_fetcher_amp_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_initialize_caches_amp_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - want_poll_for_updates_amp_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedReqestsConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_fetcher_amp_query: "old_fetcher_amp_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_initialize_caches_amp_query: "old_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - want_poll_for_updates_amp_query: "old_poll_for_updates_amp_query", - }, - { - description: "New config set, old config not set", - config: storedReqestsConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_fetcher_amp_query: "new_fetcher_amp_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_initialize_caches_amp_query: "new_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", - }, - { - description: "New config and old config set", - config: storedReqestsConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_fetcher_amp_query: "new_fetcher_amp_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_initialize_caches_amp_query: "new_initialize_caches_amp_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - want_poll_for_updates_amp_query: "new_poll_for_updates_amp_query", - }, - } - - for _, tt := range storedRequestsTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.GetString("stored_requests.database.connection.dbname"), tt.description) - assert.Equal(t, tt.want_connection_host, v.GetString("stored_requests.database.connection.host"), tt.description) - assert.Equal(t, tt.want_connection_port, v.GetInt("stored_requests.database.connection.port"), tt.description) - assert.Equal(t, tt.want_connection_user, v.GetString("stored_requests.database.connection.user"), tt.description) - assert.Equal(t, tt.want_connection_password, v.GetString("stored_requests.database.connection.password"), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.GetString("stored_requests.database.fetcher.query"), tt.description) - assert.Equal(t, tt.want_fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query"), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.GetInt("stored_requests.database.initialize_caches.timeout_ms"), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.GetString("stored_requests.database.initialize_caches.query"), tt.description) - assert.Equal(t, tt.want_initialize_caches_amp_query, v.GetString("stored_requests.database.initialize_caches.amp_query"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.GetInt("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.GetInt("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query"), tt.description) - assert.Equal(t, tt.want_poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query"), tt.description) - } else { - assert.Nil(t, v.Get("stored_requests.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.fetcher.amp_query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.initialize_caches.amp_query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.query"), tt.description) - assert.Nil(t, v.Get("stored_requests.database.poll_for_updates.amp_query"), tt.description) - } - } - - // Stored Video Reqs Config Migration - storedVideoReqsConfigs := configs{ - old: []byte(` - stored_video_req: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - `), - new: []byte(` - stored_video_req: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - both: []byte(` - stored_video_req: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - } - - storedVideoReqsTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedVideoReqsConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - }, - { - description: "New config set, old config not set", - config: storedVideoReqsConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - { - description: "New config and old config set", - config: storedVideoReqsConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - } - - for _, tt := range storedVideoReqsTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.Get("stored_video_req.database.connection.dbname").(string), tt.description) - assert.Equal(t, tt.want_connection_host, v.Get("stored_video_req.database.connection.host").(string), tt.description) - assert.Equal(t, tt.want_connection_port, v.Get("stored_video_req.database.connection.port").(int), tt.description) - assert.Equal(t, tt.want_connection_user, v.Get("stored_video_req.database.connection.user").(string), tt.description) - assert.Equal(t, tt.want_connection_password, v.Get("stored_video_req.database.connection.password").(string), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.Get("stored_video_req.database.fetcher.query").(string), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_video_req.database.initialize_caches.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_video_req.database.initialize_caches.query").(string), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_video_req.database.poll_for_updates.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_video_req.database.poll_for_updates.query").(string), tt.description) - } else { - assert.Nil(t, v.Get("stored_video_req.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_video_req.database.poll_for_updates.query"), tt.description) - } - } - - // Stored Responses Config Migration - storedResponsesConfigs := configs{ - old: []byte(` - stored_responses: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - `), - new: []byte(` - stored_responses: - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - both: []byte(` - stored_responses: - postgres: - connection: - dbname: "old_connection_dbname" - host: "old_connection_host" - port: 1000 - user: "old_connection_user" - password: "old_connection_password" - fetcher: - query: "old_fetcher_query" - initialize_caches: - timeout_ms: 1000 - query: "old_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 1000 - timeout_ms: 1000 - query: "old_poll_for_updates_query" - database: - connection: - dbname: "new_connection_dbname" - host: "new_connection_host" - port: 2000 - user: "new_connection_user" - password: "new_connection_password" - fetcher: - query: "new_fetcher_query" - initialize_caches: - timeout_ms: 2000 - query: "new_initialize_caches_query" - poll_for_updates: - refresh_rate_seconds: 2000 - timeout_ms: 2000 - query: "new_poll_for_updates_query" - `), - } - - storedResponsesTests := []struct { - description string - config []byte - - want_connection_dbname string - want_connection_host string - want_connection_port int - want_connection_user string - want_connection_password string - want_fetcher_query string - want_initialize_caches_timeout_ms int - want_initialize_caches_query string - want_poll_for_updates_refresh_rate_seconds int - want_poll_for_updates_timeout_ms int - want_poll_for_updates_query string - }{ - { - description: "New config and old config not set", - config: []byte{}, - }, - { - description: "New config not set, old config set", - config: storedResponsesConfigs.old, - - want_connection_dbname: "old_connection_dbname", - want_connection_host: "old_connection_host", - want_connection_port: 1000, - want_connection_user: "old_connection_user", - want_connection_password: "old_connection_password", - want_fetcher_query: "old_fetcher_query", - want_initialize_caches_timeout_ms: 1000, - want_initialize_caches_query: "old_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 1000, - want_poll_for_updates_timeout_ms: 1000, - want_poll_for_updates_query: "old_poll_for_updates_query", - }, - { - description: "New config set, old config not set", - config: storedResponsesConfigs.new, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - { - description: "New config and old config set", - config: storedResponsesConfigs.both, - - want_connection_dbname: "new_connection_dbname", - want_connection_host: "new_connection_host", - want_connection_port: 2000, - want_connection_user: "new_connection_user", - want_connection_password: "new_connection_password", - want_fetcher_query: "new_fetcher_query", - want_initialize_caches_timeout_ms: 2000, - want_initialize_caches_query: "new_initialize_caches_query", - want_poll_for_updates_refresh_rate_seconds: 2000, - want_poll_for_updates_timeout_ms: 2000, - want_poll_for_updates_query: "new_poll_for_updates_query", - }, - } - - for _, tt := range storedResponsesTests { - v := viper.New() - v.SetConfigType("yaml") - v.ReadConfig(bytes.NewBuffer(tt.config)) - - migrateConfigDatabaseConnection(v) - - if len(tt.config) > 0 { - assert.Equal(t, tt.want_connection_dbname, v.Get("stored_responses.database.connection.dbname").(string), tt.description) - assert.Equal(t, tt.want_connection_host, v.Get("stored_responses.database.connection.host").(string), tt.description) - assert.Equal(t, tt.want_connection_port, v.Get("stored_responses.database.connection.port").(int), tt.description) - assert.Equal(t, tt.want_connection_user, v.Get("stored_responses.database.connection.user").(string), tt.description) - assert.Equal(t, tt.want_connection_password, v.Get("stored_responses.database.connection.password").(string), tt.description) - assert.Equal(t, tt.want_fetcher_query, v.Get("stored_responses.database.fetcher.query").(string), tt.description) - assert.Equal(t, tt.want_initialize_caches_timeout_ms, v.Get("stored_responses.database.initialize_caches.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_initialize_caches_query, v.Get("stored_responses.database.initialize_caches.query").(string), tt.description) - assert.Equal(t, tt.want_poll_for_updates_refresh_rate_seconds, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_timeout_ms, v.Get("stored_responses.database.poll_for_updates.timeout_ms").(int), tt.description) - assert.Equal(t, tt.want_poll_for_updates_query, v.Get("stored_responses.database.poll_for_updates.query").(string), tt.description) - } else { - assert.Nil(t, v.Get("stored_responses.database.connection.dbname"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.host"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.port"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.user"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.connection.password"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.fetcher.query"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.initialize_caches.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.initialize_caches.query"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.refresh_rate_seconds"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.timeout_ms"), tt.description) - assert.Nil(t, v.Get("stored_responses.database.poll_for_updates.query"), tt.description) - } - } -} - -func TestMigrateConfigDatabaseConnectionUsingEnvVars(t *testing.T) { - tests := []struct { - description string - prefix string - setDatabaseEnvVars bool - setPostgresEnvVars bool - }{ - { - description: "stored requests old config set", - prefix: "stored_requests", - setPostgresEnvVars: true, - }, - { - description: "stored requests new config set", - prefix: "stored_requests", - setDatabaseEnvVars: true, - }, - { - description: "stored requests old and new config set", - prefix: "stored_requests", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - { - description: "stored video requests old config set", - prefix: "stored_video_req", - setPostgresEnvVars: true, - }, - { - description: "stored video requests new config set", - prefix: "stored_video_req", - setDatabaseEnvVars: true, - }, - { - description: "stored video requests old and new config set", - prefix: "stored_video_req", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - { - description: "stored responses old config set", - prefix: "stored_responses", - setPostgresEnvVars: true, - }, - { - description: "stored responses new config set", - prefix: "stored_responses", - setDatabaseEnvVars: true, - }, - { - description: "stored responses old and new config set", - prefix: "stored_responses", - setDatabaseEnvVars: true, - setPostgresEnvVars: true, - }, - } - - pgValues := map[string]string{ - "CONNECTION_DBNAME": "pg-dbname", - "CONNECTION_HOST": "pg-host", - "CONNECTION_PORT": "1", - "CONNECTION_USER": "pg-user", - "CONNECTION_PASSWORD": "pg-password", - "FETCHER_QUERY": "pg-fetcher-query", - "FETCHER_AMP_QUERY": "pg-fetcher-amp-query", - "INITIALIZE_CACHES_TIMEOUT_MS": "2", - "INITIALIZE_CACHES_QUERY": "pg-init-caches-query", - "INITIALIZE_CACHES_AMP_QUERY": "pg-init-caches-amp-query", - "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "3", - "POLL_FOR_UPDATES_TIMEOUT_MS": "4", - "POLL_FOR_UPDATES_QUERY": "pg-poll-query $LAST_UPDATED", - "POLL_FOR_UPDATES_AMP_QUERY": "pg-poll-amp-query $LAST_UPDATED", - } - dbValues := map[string]string{ - "CONNECTION_DBNAME": "db-dbname", - "CONNECTION_HOST": "db-host", - "CONNECTION_PORT": "5", - "CONNECTION_USER": "db-user", - "CONNECTION_PASSWORD": "db-password", - "FETCHER_QUERY": "db-fetcher-query", - "FETCHER_AMP_QUERY": "db-fetcher-amp-query", - "INITIALIZE_CACHES_TIMEOUT_MS": "6", - "INITIALIZE_CACHES_QUERY": "db-init-caches-query", - "INITIALIZE_CACHES_AMP_QUERY": "db-init-caches-amp-query", - "POLL_FOR_UPDATES_REFRESH_RATE_SECONDS": "7", - "POLL_FOR_UPDATES_TIMEOUT_MS": "8", - "POLL_FOR_UPDATES_QUERY": "db-poll-query $LAST_UPDATED", - "POLL_FOR_UPDATES_AMP_QUERY": "db-poll-amp-query $LAST_UPDATED", - } - - for _, tt := range tests { - t.Run(tt.description, func(t *testing.T) { - prefix := "PBS_" + strings.ToUpper(tt.prefix) - - // validation rules require in memory cache type to not be "none" - // given that we want to set the poll for update queries to non-empty values - envVarName := prefix + "_IN_MEMORY_CACHE_TYPE" - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, "unbounded") - - if tt.setPostgresEnvVars { - for suffix, v := range pgValues { - envVarName := prefix + "_POSTGRES_" + suffix - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, v) - } - } - if tt.setDatabaseEnvVars { - for suffix, v := range dbValues { - envVarName := prefix + "_DATABASE_" + suffix - if oldval, ok := os.LookupEnv(envVarName); ok { - defer os.Setenv(envVarName, oldval) - } else { - defer os.Unsetenv(envVarName) - } - os.Setenv(envVarName, v) - } - } - - c, _ := newDefaultConfig(t) - - expectedDatabaseValues := map[string]string{} - if tt.setDatabaseEnvVars { - expectedDatabaseValues = dbValues - } else if tt.setPostgresEnvVars { - expectedDatabaseValues = pgValues - } - - if tt.prefix == "stored_requests" { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredRequests.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredRequests.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredRequests.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredRequests.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredRequests.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredRequests.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredRequests.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredRequests.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredRequests.Database.PollUpdates.Query, tt.description) - // AMP queries are only migrated for stored requests - assert.Equal(t, expectedDatabaseValues["FETCHER_AMP_QUERY"], c.StoredRequests.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_AMP_QUERY"], c.StoredRequests.Database.CacheInitialization.AmpQuery, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_AMP_QUERY"], c.StoredRequests.Database.PollUpdates.AmpQuery, tt.description) - } else if tt.prefix == "stored_video_req" { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredVideo.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredVideo.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredVideo.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredVideo.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredVideo.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredVideo.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredVideo.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredVideo.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredVideo.Database.PollUpdates.Query, tt.description) - assert.Empty(t, c.StoredVideo.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Empty(t, c.StoredVideo.Database.CacheInitialization.AmpQuery, tt.description) - assert.Empty(t, c.StoredVideo.Database.PollUpdates.AmpQuery, tt.description) - } else { - assert.Equal(t, expectedDatabaseValues["CONNECTION_DBNAME"], c.StoredResponses.Database.ConnectionInfo.Database, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_HOST"], c.StoredResponses.Database.ConnectionInfo.Host, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PORT"], strconv.Itoa(c.StoredResponses.Database.ConnectionInfo.Port), tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_USER"], c.StoredResponses.Database.ConnectionInfo.Username, tt.description) - assert.Equal(t, expectedDatabaseValues["CONNECTION_PASSWORD"], c.StoredResponses.Database.ConnectionInfo.Password, tt.description) - assert.Equal(t, expectedDatabaseValues["FETCHER_QUERY"], c.StoredResponses.Database.FetcherQueries.QueryTemplate, tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.CacheInitialization.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["INITIALIZE_CACHES_QUERY"], c.StoredResponses.Database.CacheInitialization.Query, tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_REFRESH_RATE_SECONDS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.RefreshRate), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_TIMEOUT_MS"], strconv.Itoa(c.StoredResponses.Database.PollUpdates.Timeout), tt.description) - assert.Equal(t, expectedDatabaseValues["POLL_FOR_UPDATES_QUERY"], c.StoredResponses.Database.PollUpdates.Query, tt.description) - assert.Empty(t, c.StoredResponses.Database.FetcherQueries.AmpQueryTemplate, tt.description) - assert.Empty(t, c.StoredResponses.Database.CacheInitialization.AmpQuery, tt.description) - assert.Empty(t, c.StoredResponses.Database.PollUpdates.AmpQuery, tt.description) - } - }) - } -} - -func TestMigrateConfigDatabaseQueryParams(t *testing.T) { - - config := []byte(` - stored_requests: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - stored_video_req: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - stored_responses: - postgres: - fetcher: - query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - amp_query: - SELECT * FROM Table1 WHERE id in (%REQUEST_ID_LIST%) - UNION ALL - SELECT * FROM Table2 WHERE id in (%IMP_ID_LIST%) - UNION ALL - SELECT * FROM Table3 WHERE id in (%ID_LIST%) - poll_for_updates: - query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - amp_query: "SELECT * FROM Table1 WHERE last_updated > $1 UNION ALL SELECT * FROM Table2 WHERE last_updated > $1" - `) - - want_queries := struct { - fetcher_query string - fetcher_amp_query string - poll_for_updates_query string - poll_for_updates_amp_query string - }{ - fetcher_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table3 WHERE id in ($ID_LIST)", - fetcher_amp_query: "SELECT * FROM Table1 WHERE id in ($REQUEST_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table2 WHERE id in ($IMP_ID_LIST) " + - "UNION ALL " + - "SELECT * FROM Table3 WHERE id in ($ID_LIST)", - poll_for_updates_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", - poll_for_updates_amp_query: "SELECT * FROM Table1 WHERE last_updated > $LAST_UPDATED UNION ALL SELECT * FROM Table2 WHERE last_updated > $LAST_UPDATED", - } - - v := viper.New() - v.SetConfigType("yaml") - err := v.ReadConfig(bytes.NewBuffer(config)) - assert.NoError(t, err) - - migrateConfigDatabaseConnection(v) - - // stored_requests queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_requests.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_requests.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_requests.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_requests.database.poll_for_updates.amp_query")) - - // stored_video_req queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_video_req.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_video_req.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_video_req.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_video_req.database.poll_for_updates.amp_query")) - - // stored_responses queries - assert.Equal(t, want_queries.fetcher_query, v.GetString("stored_responses.database.fetcher.query")) - assert.Equal(t, want_queries.fetcher_amp_query, v.GetString("stored_responses.database.fetcher.amp_query")) - assert.Equal(t, want_queries.poll_for_updates_query, v.GetString("stored_responses.database.poll_for_updates.query")) - assert.Equal(t, want_queries.poll_for_updates_amp_query, v.GetString("stored_responses.database.poll_for_updates.amp_query")) -} - -func TestMigrateConfigCompression(t *testing.T) { - testCases := []struct { - desc string - config []byte - wantEnableGZIP bool - wantReqGZIPEnabled bool - wantRespGZIPEnabled bool - }{ - - { - desc: "New config and old config not set", - config: []byte{}, - wantEnableGZIP: false, - wantReqGZIPEnabled: false, - wantRespGZIPEnabled: false, - }, - { - desc: "Old config set, new config not set", - config: []byte(` - enable_gzip: true - `), - wantEnableGZIP: true, - wantRespGZIPEnabled: true, - wantReqGZIPEnabled: false, - }, - { - desc: "Old config not set, new config set", - config: []byte(` - compression: - response: - enable_gzip: true - request: - enable_gzip: false - `), - wantEnableGZIP: false, - wantRespGZIPEnabled: true, - wantReqGZIPEnabled: false, - }, - { - desc: "Old config set and new config set", - config: []byte(` - enable_gzip: true - compression: - response: - enable_gzip: false - request: - enable_gzip: true - `), - wantEnableGZIP: true, - wantRespGZIPEnabled: false, - wantReqGZIPEnabled: true, - }, - } - - for _, test := range testCases { - v := viper.New() - v.SetConfigType("yaml") - err := v.ReadConfig(bytes.NewBuffer(test.config)) - assert.NoError(t, err) - - migrateConfigCompression(v) - - assert.Equal(t, test.wantEnableGZIP, v.GetBool("enable_gzip"), test.desc) - assert.Equal(t, test.wantReqGZIPEnabled, v.GetBool("compression.request.enable_gzip"), test.desc) - assert.Equal(t, test.wantRespGZIPEnabled, v.GetBool("compression.response.enable_gzip"), test.desc) - } -} - func TestIsConfigInfoPresent(t *testing.T) { configPrefix1Field2Only := []byte(` prefix1: @@ -3028,6 +1357,155 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) { } } +func TestSetConfigBidderInfoNillableFields(t *testing.T) { + falseValue := false + trueValue := true + + bidder1ConfigFalses := []byte(` + adapters: + bidder1: + disabled: false + modifyingVastXmlAllowed: false`) + bidder1ConfigTrues := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: true`) + bidder1ConfigNils := []byte(` + adapters: + bidder1: + disabled: null + modifyingVastXmlAllowed: null`) + bidder1Bidder2ConfigMixed := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: false + bidder2: + disabled: false + modifyingVastXmlAllowed: true`) + + tests := []struct { + name string + rawConfig []byte + bidderInfos BidderInfos + expected nillableFieldBidderInfos + expectError bool + }{ + { + name: "viper and bidder infos are nil", + expected: nil, + }, + { + name: "viper is nil", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "bidder infos is nil", + rawConfig: []byte{}, + expected: nil, + }, + { + name: "bidder infos is empty", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "one: bidder info has nillable fields as false, viper has as nil", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigNils, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: nil, + ModifyingVastXmlAllowed: nil, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigFalses, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as true", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigTrues, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "many with extra info: bidder infos have nillable fields as false and true, viper has as true and false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + "bidder2": {Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + rawConfig: bidder1Bidder2ConfigMixed, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + }, + "bidder2": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := viper.New() + v.SetConfigType("yaml") + for bidderName := range tt.bidderInfos { + setBidderDefaults(v, strings.ToLower(bidderName)) + } + v.ReadConfig(bytes.NewBuffer(tt.rawConfig)) + + result, err := setConfigBidderInfoNillableFields(v, tt.bidderInfos) + + assert.Equal(t, tt.expected, result) + if tt.expectError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + func TestTCF2PurposeEnforced(t *testing.T) { tests := []struct { description string @@ -3304,52 +1782,3 @@ func TestTCF2FeatureOneVendorException(t *testing.T) { assert.Equal(t, tt.wantIsVendorException, value, tt.description) } } - -func TestMigrateConfigEventsEnabled(t *testing.T) { - testCases := []struct { - name string - oldFieldValue *bool - newFieldValue *bool - expectedOldFieldValue *bool - expectedNewFieldValue *bool - }{ - { - name: "Both old and new fields are nil", - oldFieldValue: nil, - newFieldValue: nil, - expectedOldFieldValue: nil, - expectedNewFieldValue: nil, - }, - { - name: "Only old field is set", - oldFieldValue: ptrutil.ToPtr(true), - newFieldValue: nil, - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - { - name: "Only new field is set", - oldFieldValue: nil, - newFieldValue: ptrutil.ToPtr(true), - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - { - name: "Both old and new fields are set, override old field with new field value", - oldFieldValue: ptrutil.ToPtr(false), - newFieldValue: ptrutil.ToPtr(true), - expectedOldFieldValue: ptrutil.ToPtr(true), - expectedNewFieldValue: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - updatedOldFieldValue, updatedNewFieldValue := migrateConfigEventsEnabled(tc.oldFieldValue, tc.newFieldValue) - - assert.Equal(t, tc.expectedOldFieldValue, updatedOldFieldValue) - assert.Nil(t, updatedNewFieldValue) - assert.Nil(t, tc.expectedNewFieldValue) - }) - } -} diff --git a/config/events.go b/config/events.go index 83d2df4b58d..cf3139d83ca 100644 --- a/config/events.go +++ b/config/events.go @@ -61,14 +61,14 @@ type VASTEvent struct { // within the VAST XML // Don't enable this feature. It is still under developmment. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates type Events struct { - Enabled *bool `mapstructure:"enabled" json:"enabled"` + Enabled bool `mapstructure:"enabled" json:"enabled"` DefaultURL string `mapstructure:"default_url" json:"default_url"` VASTEvents []VASTEvent `mapstructure:"vast_events" json:"vast_events,omitempty"` } // validate verifies the events object and returns error if at least one is invalid. func (e Events) validate(errs []error) []error { - if e.IsEnabled() { + if e.Enabled { if !isValidURL(e.DefaultURL) { return append(errs, errors.New("Invalid events.default_url")) } @@ -147,8 +147,3 @@ func isValidURL(eventURL string) bool { func (e VASTEvent) isTrackingEvent() bool { return e.CreateElement == TrackingVASTElement } - -// IsEnabled function returns the value of events.enabled field -func (e Events) IsEnabled() bool { - return e.Enabled != nil && *e.Enabled -} diff --git a/config/events_test.go b/config/events_test.go index 4baa9066dec..b4c196e9fa9 100644 --- a/config/events_test.go +++ b/config/events_test.go @@ -3,7 +3,6 @@ package config import ( "testing" - "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -262,29 +261,22 @@ func TestValidate(t *testing.T) { { description: "Empty default URL", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, }, expectErr: true, }, { description: "Events are disabled. Skips validations", events: Events{ - Enabled: ptrutil.ToPtr(false), + Enabled: false, DefaultURL: "", }, expectErr: false, }, - { - description: "Events are nil. Skip validations", - events: Events{ - Enabled: nil, - }, - expectErr: false, - }, { description: "No VAST Events and default URL present", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, DefaultURL: "http://prebid.org", }, expectErr: false, @@ -292,7 +284,7 @@ func TestValidate(t *testing.T) { { description: "Invalid VAST Event", events: Events{ - Enabled: ptrutil.ToPtr(true), + Enabled: true, DefaultURL: "http://prebid.org", VASTEvents: []VASTEvent{ {}, @@ -335,34 +327,3 @@ func TestValidateVASTEvents(t *testing.T) { assert.Equal(t, !test.expectErr, err == nil, test.description) } } - -func TestIsEnabled(t *testing.T) { - testCases := []struct { - name string - events Events - expected bool - }{ - { - name: "nil pointer", - events: Events{}, - expected: false, - }, - { - name: "event false", - events: Events{Enabled: ptrutil.ToPtr(false)}, - expected: false, - }, - { - name: "event true", - events: Events{Enabled: ptrutil.ToPtr(true)}, - expected: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := tc.events.IsEnabled() - assert.Equal(t, tc.expected, actual) - }) - } -} diff --git a/config/test/bidder-info-valid/stroeerCore.yaml b/config/test/bidder-info-valid/stroeerCore.yaml index 0a1b3059cd4..ef95d2388a5 100644 --- a/config/test/bidder-info-valid/stroeerCore.yaml +++ b/config/test/bidder-info-valid/stroeerCore.yaml @@ -11,6 +11,10 @@ capabilities: - banner - video - native + dooh: + mediaTypes: + - banner + - video userSync: key: "foo" default: "iframe" diff --git a/config/usersync.go b/config/usersync.go index 27badbcb815..d490853d08e 100644 --- a/config/usersync.go +++ b/config/usersync.go @@ -2,13 +2,13 @@ package config // UserSync specifies the static global user sync configuration. type UserSync struct { - Cooperative UserSyncCooperative `mapstructure:"coop_sync"` - ExternalURL string `mapstructure:"external_url"` - RedirectURL string `mapstructure:"redirect_url"` + Cooperative UserSyncCooperative `mapstructure:"coop_sync"` + ExternalURL string `mapstructure:"external_url"` + RedirectURL string `mapstructure:"redirect_url"` + PriorityGroups [][]string `mapstructure:"priority_groups"` } // UserSyncCooperative specifies the static global default cooperative cookie sync type UserSyncCooperative struct { - EnabledByDefault bool `mapstructure:"default"` - PriorityGroups [][]string `mapstructure:"priority_groups"` + EnabledByDefault bool `mapstructure:"default"` } diff --git a/currency/currency.go b/currency/currency.go new file mode 100644 index 00000000000..060649f9c11 --- /dev/null +++ b/currency/currency.go @@ -0,0 +1,42 @@ +package currency + +import ( + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +func GetAuctionCurrencyRates(currencyConverter *RateConverter, requestRates *openrtb_ext.ExtRequestCurrency) Conversions { + if currencyConverter == nil && requestRates == nil { + return nil + } + + if requestRates == nil { + // No bidRequest.ext.currency field was found, use PBS rates as usual + return currencyConverter.Rates() + } + + // currencyConverter will never be nil, refer main.serve(), adding this check for future usecases + if currencyConverter == nil { + return NewRates(requestRates.ConversionRates) + } + + // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false + // only if it's explicitly set to false + usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates + + if !usePbsRates { + // At this point, we can safely assume the ConversionRates map is not empty because + // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have + // thrown an error under such conditions. + return NewRates(requestRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(requestRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return currencyConverter.Rates() + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return NewAggregateConversions(NewRates(requestRates.ConversionRates), currencyConverter.Rates()) +} diff --git a/currency/currency_mock.go b/currency/currency_mock.go new file mode 100644 index 00000000000..f25974380c7 --- /dev/null +++ b/currency/currency_mock.go @@ -0,0 +1,20 @@ +package currency + +import ( + "io" + "net/http" + "strings" +) + +// MockCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type MockCurrencyRatesHttpClient struct { + ResponseBody string +} + +func (m *MockCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(m.ResponseBody)), + }, nil +} diff --git a/currency/currency_test.go b/currency/currency_test.go new file mode 100644 index 00000000000..d462cc8bdab --- /dev/null +++ b/currency/currency_test.go @@ -0,0 +1,199 @@ +package currency + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestGetAuctionCurrencyRates(t *testing.T) { + pbsRates := map[string]map[string]float64{ + "MXN": { + "USD": 20.13, + "EUR": 27.82, + "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates + }, + } + + customRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // different rate than in pbsRates + "EUR": 27.82, // same as in pbsRates + "GBP": 31.12, // not found in pbsRates at all + }, + } + + expectedRateEngineRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // rates engine will prioritize the value found in custom rates + "EUR": 27.82, // same value in both the engine reads the custom entry first + "JPY": 5.09, // the engine will find it in the pbsRates conversions + "GBP": 31.12, // the engine will find it in the custom conversions + }, + } + + setupMockRateConverter := func(pbsRates map[string]map[string]float64) *RateConverter { + if pbsRates == nil { + return nil + } + + jsonPbsRates, err := jsonutil.Marshal(pbsRates) + if err != nil { + t.Fatalf("Failed to marshal PBS rates: %v", err) + } + + // Init mock currency conversion service + mockCurrencyClient := &MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, + } + + return NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + } + + type args struct { + currencyConverter *RateConverter + requestRates *openrtb_ext.ExtRequestCurrency + } + tests := []struct { + name string + args args + assertRates map[string]map[string]float64 + }{ + { + name: "valid ConversionRates, valid pbsRates, false UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: customRates, + }, + { + name: "valid ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: expectedRateEngineRates, + }, + { + name: "valid ConversionRates, nil pbsRates, false UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: customRates, + }, + { + name: "valid ConversionRates, nil pbsRates, true UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: customRates, + }, + { + name: "empty ConversionRates, valid pbsRates, false UsePBSRates. Because pbsRates cannot be used, disable currency conversion", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: nil, + }, + { + name: "nil ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates will be identical to pbsRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: nil, + }, + assertRates: pbsRates, + }, + { + name: "empty ConversionRates, nil pbsRates, false UsePBSRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: nil, + }, + + { + name: "empty ConversionRates, nil pbsRates, true UsePBSRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: nil, + }, + { + name: "nil customRates, nil pbsRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: nil, + requestRates: nil, + }, + assertRates: nil, + }, + { + name: "empty ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates will be identical to pbsRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: pbsRates, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.currencyConverter != nil { + tt.args.currencyConverter.Run() + } + auctionRates := GetAuctionCurrencyRates(tt.args.currencyConverter, tt.args.requestRates) + if tt.args.currencyConverter == nil && tt.args.requestRates == nil && tt.assertRates == nil { + assert.Nil(t, auctionRates) + } else if tt.assertRates == nil { + rate, err := auctionRates.GetRate("USD", "MXN") + assert.Error(t, err, tt.name) + assert.Equal(t, float64(0), rate, tt.name) + } else { + for fromCurrency, rates := range tt.assertRates { + for toCurrency, expectedRate := range rates { + actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) + assert.NoError(t, err, tt.name) + assert.Equal(t, expectedRate, actualRate, tt.name) + } + } + } + }) + } +} diff --git a/currency/rate_converter.go b/currency/rate_converter.go index f28807701ae..a9ab9547f83 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -1,7 +1,6 @@ package currency import ( - "encoding/json" "fmt" "io" "net/http" @@ -9,8 +8,9 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/timeutil" ) // RateConverter holds the currencies conversion rates dictionary @@ -66,7 +66,7 @@ func (rc *RateConverter) fetch() (*Rates, error) { } updatedRates := &Rates{} - err = json.Unmarshal(bytesJSON, updatedRates) + err = jsonutil.UnmarshalValid(bytesJSON, updatedRates) if err != nil { return nil, err } diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index 617aa02e96a..96003c7d986 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" "github.com/stretchr/testify/assert" ) diff --git a/currency/rates_test.go b/currency/rates_test.go index 23226dce8fb..254bc282dec 100644 --- a/currency/rates_test.go +++ b/currency/rates_test.go @@ -1,11 +1,12 @@ package currency import ( - "encoding/json" "errors" "testing" "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestUnMarshallRates(t *testing.T) { @@ -22,7 +23,7 @@ func TestUnMarshallRates(t *testing.T) { ratesJSON: `malformed`, expectedRates: Rates{}, expectsError: true, - expectedError: errors.New("invalid character 'm' looking for beginning of value"), + expectedError: errors.New("expect { or n, but found m"), }, { desc: "Valid JSON field defining valid conversion object. Expect no error", @@ -50,7 +51,7 @@ func TestUnMarshallRates(t *testing.T) { expectedError: nil, }, { - desc: "Valid JSON field defines a conversions map with repeated entries, expect error", + desc: "Valid JSON field defines a conversions map with repeated entries, last one wins", ratesJSON: `{ "conversions":{ "USD":{ @@ -58,25 +59,31 @@ func TestUnMarshallRates(t *testing.T) { "MXN":20.00 }, "USD":{ - "GBP":0.7662523901 - }, + "GBP":0.4815162342 + } } }`, - expectedRates: Rates{}, - expectsError: true, - expectedError: errors.New("invalid character '}' looking for beginning of object key string"), + expectedRates: Rates{ + Conversions: map[string]map[string]float64{ + "USD": { + "GBP": 0.4815162342, + }, + }, + }, + expectsError: false, + expectedError: nil, }, } for _, tc := range testCases { // Execute: updatedRates := Rates{} - err := json.Unmarshal([]byte(tc.ratesJSON), &updatedRates) + err := jsonutil.UnmarshalValid([]byte(tc.ratesJSON), &updatedRates) // Verify: assert.Equal(t, err != nil, tc.expectsError, tc.desc) if tc.expectsError { - assert.Equal(t, err.Error(), tc.expectedError.Error(), tc.desc) + assert.Equal(t, tc.expectedError.Error(), err.Error(), tc.desc) } assert.Equal(t, tc.expectedRates, updatedRates, tc.desc) } diff --git a/currency/validation.go b/currency/validation.go index 7a0e2aa02bd..d6429c357b6 100644 --- a/currency/validation.go +++ b/currency/validation.go @@ -5,8 +5,8 @@ import ( "golang.org/x/text/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ValidateCustomRates throws a bad input error if any of the 3-digit currency codes found in diff --git a/currency/validation_test.go b/currency/validation_test.go index d49b9824986..65f93a5f9e9 100644 --- a/currency/validation_test.go +++ b/currency/validation_test.go @@ -3,8 +3,8 @@ package currency import ( "testing" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index fa154bbcbff..ef32e4048d7 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -14,20 +14,21 @@ import ( "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - stringutil "github.com/prebid/prebid-server/util/stringutil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + gppPrivacy "github.com/prebid/prebid-server/v2/privacy/gpp" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + stringutil "github.com/prebid/prebid-server/v2/util/stringutil" ) var ( @@ -39,6 +40,7 @@ var ( errCookieSyncAccountBlocked = errors.New("account is disabled, please reach out to the prebid server host") errCookieSyncAccountConfigMalformed = errors.New("account config is malformed and could not be read") errCookieSyncAccountInvalid = errors.New("account must be valid if provided, please reach out to the prebid server host") + errSyncerIsNotPriority = errors.New("syncer key is not a priority, and there are only priority elements left") ) var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude) @@ -49,7 +51,7 @@ func NewCookieSyncEndpoint( gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, metrics metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, accountsFetcher stored_requests.AccountFetcher, bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler { @@ -59,7 +61,7 @@ func NewCookieSyncEndpoint( } return &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, bidderHashSet, config.BidderInfos), config: config, privacyConfig: usersyncPrivacyConfig{ gdprConfig: config.GDPR, @@ -69,7 +71,7 @@ func NewCookieSyncEndpoint( bidderHashSet: bidderHashSet, }, metrics: metrics, - pbsAnalytics: pbsAnalytics, + pbsAnalytics: analyticsRunner, accountsFetcher: accountsFetcher, } } @@ -79,45 +81,48 @@ type cookieSyncEndpoint struct { config *config.Configuration privacyConfig usersyncPrivacyConfig metrics metrics.MetricsEngine - pbsAnalytics analytics.PBSAnalyticsModule + pbsAnalytics analytics.Runner accountsFetcher stored_requests.AccountFetcher } func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - request, privacyPolicies, err := c.parseRequest(r) + request, privacyMacros, err := c.parseRequest(r) if err != nil { c.writeParseRequestErrorMetrics(err) c.handleError(w, err, http.StatusBadRequest) return } + decoder := usersync.Base64Decoder{} - cookie := usersync.ParseCookieFromRequest(r, &c.config.HostCookie) + cookie := usersync.ReadCookie(r, decoder, &c.config.HostCookie) + usersync.SyncHostCookie(r, cookie, &c.config.HostCookie) result := c.chooser.Choose(request, cookie) + switch result.Status { case usersync.StatusBlockedByUserOptOut: c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) - case usersync.StatusBlockedByGDPR: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyPolicies, nil) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil, result.BiddersEvaluated, request.Debug) case usersync.StatusOK: c.metrics.RecordCookieSync(metrics.CookieSyncOK) c.writeSyncerMetrics(result.BiddersEvaluated) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyPolicies, result.SyncersChosen) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen, result.BiddersEvaluated, request.Debug) } } -func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, privacy.Policies, error) { +func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, error) { defer r.Body.Close() body, err := io.ReadAll(r.Body) if err != nil { - return usersync.Request{}, privacy.Policies{}, errCookieSyncBody + return usersync.Request{}, macros.UserSyncPrivacy{}, errCookieSyncBody } request := cookieSyncRequest{} - if err := json.Unmarshal(body, &request); err != nil { - return usersync.Request{}, privacy.Policies{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) + if err := jsonutil.UnmarshalValid(body, &request); err != nil { + return usersync.Request{}, macros.UserSyncPrivacy{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) } if request.Account == "" { @@ -125,35 +130,37 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr } account, fetchErrs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account, c.metrics) if len(fetchErrs) > 0 { - return usersync.Request{}, privacy.Policies{}, combineErrors(fetchErrs) + return usersync.Request{}, macros.UserSyncPrivacy{}, combineErrors(fetchErrs) } request = c.setLimit(request, account.CookieSync) request = c.setCooperativeSync(request, account.CookieSync) - privacyPolicies, gdprSignal, err := extractPrivacyPolicies(request, c.privacyConfig.gdprConfig.DefaultValue) + privacyMacros, gdprSignal, privacyPolicies, err := extractPrivacyPolicies(request, c.privacyConfig.gdprConfig.DefaultValue) if err != nil { - return usersync.Request{}, privacy.Policies{}, err + return usersync.Request{}, macros.UserSyncPrivacy{}, err } ccpaParsedPolicy := ccpa.ParsedPolicy{} if request.USPrivacy != "" { - parsedPolicy, err := privacyPolicies.CCPA.Parse(c.privacyConfig.bidderHashSet) + parsedPolicy, err := ccpa.Policy{Consent: request.USPrivacy}.Parse(c.privacyConfig.bidderHashSet) if err != nil { - privacyPolicies.CCPA.Consent = "" + privacyMacros.USPrivacy = "" } if c.privacyConfig.ccpaEnforce { ccpaParsedPolicy = parsedPolicy } } + activityControl := privacy.NewActivityControl(&account.Privacy) + syncTypeFilter, err := parseTypeFilter(request.FilterSettings) if err != nil { - return usersync.Request{}, privacy.Policies{}, err + return usersync.Request{}, macros.UserSyncPrivacy{}, err } gdprRequestInfo := gdpr.RequestInfo{ - Consent: privacyPolicies.GDPR.Consent, + Consent: privacyMacros.GDPRConsent, GDPRSignal: gdprSignal, } @@ -164,28 +171,33 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr Bidders: request.Bidders, Cooperative: usersync.Cooperative{ Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault), - PriorityGroups: c.config.UserSync.Cooperative.PriorityGroups, + PriorityGroups: c.config.UserSync.PriorityGroups, }, + Debug: request.Debug, Limit: request.Limit, Privacy: usersyncPrivacy{ gdprPermissions: gdprPerms, ccpaParsedPolicy: ccpaParsedPolicy, + activityControl: activityControl, + activityRequest: privacy.NewRequestFromPolicies(privacyPolicies), + gdprSignal: gdprSignal, }, SyncTypeFilter: syncTypeFilter, + GPPSID: request.GPPSID, } - return rx, privacyPolicies, nil + return rx, privacyMacros, nil } -func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue string) (privacy.Policies, gdpr.Signal, error) { +func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue string) (macros.UserSyncPrivacy, gdpr.Signal, privacy.Policies, error) { // GDPR - gppSID, err := stringutil.StrToInt8Slice(request.GPPSid) + gppSID, err := stringutil.StrToInt8Slice(request.GPPSID) if err != nil { - return privacy.Policies{}, gdpr.SignalNo, err + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err } gdprSignal, gdprString, err := extractGDPRSignal(request.GDPR, gppSID) if err != nil { - return privacy.Policies{}, gdpr.SignalNo, err + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err } var gpp gpplib.GppContainer @@ -193,7 +205,7 @@ func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue var err error gpp, err = gpplib.Parse(request.GPP) if err != nil { - return privacy.Policies{}, gdpr.SignalNo, err + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err } } @@ -204,33 +216,33 @@ func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue if gdprConsent == "" { if gdprSignal == gdpr.SignalYes { - return privacy.Policies{}, gdpr.SignalNo, errCookieSyncGDPRConsentMissing + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errCookieSyncGDPRConsentMissing } if gdprSignal == gdpr.SignalAmbiguous && gdpr.SignalNormalize(gdprSignal, usersyncDefaultGDPRValue) == gdpr.SignalYes { - return privacy.Policies{}, gdpr.SignalNo, errCookieSyncGDPRConsentMissingSignalAmbiguous + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errCookieSyncGDPRConsentMissingSignalAmbiguous } } // CCPA ccpaString, err := ccpa.SelectCCPAConsent(request.USPrivacy, gpp, gppSID) if err != nil { - return privacy.Policies{}, gdpr.SignalNo, err + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err } - return privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: gdprString, - Consent: gdprConsent, - }, - CCPA: ccpa.Policy{ - Consent: ccpaString, - }, - GPP: gppPrivacy.Policy{ - Consent: request.GPP, - RawSID: request.GPPSid, - }, - }, gdprSignal, nil + privacyMacros := macros.UserSyncPrivacy{ + GDPR: gdprString, + GDPRConsent: gdprConsent, + USPrivacy: ccpaString, + GPP: request.GPP, + GPPSID: request.GPPSID, + } + + privacyPolicies := privacy.Policies{ + GPPSID: gppSID, + } + + return privacyMacros, gdprSignal, privacyPolicies, nil } func extractGDPRSignal(requestGDPR *int, gppSID []int8) (gdpr.Signal, string, error) { @@ -360,7 +372,7 @@ func combineErrors(errs []error) error { for _, err := range errs { // preserve knowledge of special account errors switch errortypes.ReadCode(err) { - case errortypes.BlacklistedAcctErrorCode: + case errortypes.AccountDisabledErrorCode: return errCookieSyncAccountBlocked case errortypes.AcctRequiredErrorCode: return errCookieSyncAccountInvalid @@ -379,9 +391,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd switch bidder.Status { case usersync.StatusOK: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) - case usersync.StatusBlockedByGDPR: - c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) - case usersync.StatusBlockedByCCPA: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) case usersync.StatusAlreadySynced: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) @@ -391,7 +401,7 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd } } -func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, p privacy.Policies, s []usersync.SyncerChoice) { +func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice, biddersEvaluated []usersync.BidderEvaluation, debug bool) { status := "no_cookie" if co.HasAnyLiveSyncs() { status = "ok" @@ -404,7 +414,7 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S for _, syncerChoice := range s { syncTypes := tf.ForBidder(syncerChoice.Bidder) - sync, err := syncerChoice.Syncer.GetSync(syncTypes, p) + sync, err := syncerChoice.Syncer.GetSync(syncTypes, m) if err != nil { glog.Errorf("Failed to get usersync info for %s: %v", syncerChoice.Bidder, err) continue @@ -421,6 +431,24 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S }) } + if debug { + biddersSeen := make(map[string]struct{}) + var debugInfo []cookieSyncResponseDebug + for _, bidderEval := range biddersEvaluated { + var debugResponse cookieSyncResponseDebug + debugResponse.Bidder = bidderEval.Bidder + if bidderEval.Status == usersync.StatusDuplicate && biddersSeen[bidderEval.Bidder] == struct{}{} { + debugResponse.Error = getDebugMessage(bidderEval.Status) + " synced as " + bidderEval.SyncerKey + debugInfo = append(debugInfo, debugResponse) + } else if bidderEval.Status != usersync.StatusOK { + debugResponse.Error = getDebugMessage(bidderEval.Status) + debugInfo = append(debugInfo, debugResponse) + } + biddersSeen[bidderEval.Bidder] = struct{}{} + } + response.Debug = debugInfo + } + c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ Status: http.StatusOK, BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), @@ -448,6 +476,28 @@ func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.Co return to } +func getDebugMessage(status usersync.Status) string { + switch status { + case usersync.StatusAlreadySynced: + return "Already in sync" + case usersync.StatusBlockedByPrivacy: + return "Rejected by privacy" + case usersync.StatusBlockedByUserOptOut: + return "Status blocked by user opt out" + case usersync.StatusDuplicate: + return "Duplicate bidder" + case usersync.StatusUnknownBidder: + return "Unsupported bidder" + case usersync.StatusUnconfiguredBidder: + return "No sync config" + case usersync.StatusTypeNotSupported: + return "Type not supported" + case usersync.StatusBlockedByDisabledUsersync: + return "Sync disabled by config" + } + return "" +} + type cookieSyncRequest struct { Bidders []string `json:"bidders"` GDPR *int `json:"gdpr"` @@ -455,10 +505,11 @@ type cookieSyncRequest struct { USPrivacy string `json:"us_privacy"` Limit int `json:"limit"` GPP string `json:"gpp"` - GPPSid string `json:"gpp_sid"` + GPPSID string `json:"gpp_sid"` CooperativeSync *bool `json:"coopSync"` FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` Account string `json:"account"` + Debug bool `json:"debug"` } type cookieSyncRequestFilterSettings struct { @@ -474,6 +525,7 @@ type cookieSyncRequestFilter struct { type cookieSyncResponse struct { Status string `json:"status"` BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` + Debug []cookieSyncResponseDebug `json:"debug,omitempty"` } type cookieSyncResponseBidder struct { @@ -488,6 +540,11 @@ type cookieSyncResponseSync struct { SupportCORS bool `json:"supportCORS,omitempty"` } +type cookieSyncResponseDebug struct { + Bidder string `json:"bidder"` + Error string `json:"error,omitempty"` +} + type usersyncPrivacyConfig struct { gdprConfig config.GDPR gdprPermissionsBuilder gdpr.PermissionsBuilder @@ -499,6 +556,9 @@ type usersyncPrivacyConfig struct { type usersyncPrivacy struct { gdprPermissions gdpr.Permissions ccpaParsedPolicy ccpa.ParsedPolicy + activityControl privacy.ActivityControl + activityRequest privacy.ActivityRequest + gdprSignal gdpr.Signal } func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { @@ -515,3 +575,14 @@ func (p usersyncPrivacy) CCPAAllowsBidderSync(bidder string) bool { enforce := p.ccpaParsedPolicy.CanEnforce() && p.ccpaParsedPolicy.ShouldEnforce(bidder) return !enforce } + +func (p usersyncPrivacy) ActivityAllowsUserSync(bidder string) bool { + return p.activityControl.Allow( + privacy.ActivitySyncUser, + privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidder}, + p.activityRequest) +} + +func (p usersyncPrivacy) GDPRInScope() bool { + return p.gdprSignal == gdpr.SignalYes +} diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 37acf0c2add..adfdcb22fab 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -11,20 +11,18 @@ import ( "strings" "testing" "testing/iotest" - "time" - - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/ptrutil" + + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -44,18 +42,21 @@ func TestNewCookieSyncEndpoint(t *testing.T) { configGDPR = config.GDPR{HostVendorID: 42} configCCPAEnforce = true metrics = metrics.MetricsEngineMock{} - analytics = MockAnalytics{} + analytics = MockAnalyticsRunner{} fetcher = FakeAccountsFetcher{} bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} + bidderInfo = map[string]config.BidderInfo{"bidderA": {}, "bidderB": {}} + biddersKnown = map[string]struct{}{"bidderA": {}, "bidderB": {}} ) endpoint := NewCookieSyncEndpoint( syncersByBidder, &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, gdprPermsBuilder, tcf2ConfigBuilder, @@ -67,12 +68,13 @@ func TestNewCookieSyncEndpoint(t *testing.T) { result := endpoint.(*cookieSyncEndpoint) expected := &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, biddersKnown, bidderInfo), config: &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, privacyConfig: usersyncPrivacyConfig{ gdprConfig: configGDPR, @@ -89,7 +91,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) { assert.IsType(t, &cookieSyncEndpoint{}, endpoint) assert.Equal(t, expected.config, result.config) - assert.Equal(t, expected.chooser, result.chooser) + assert.ObjectsAreEqualValues(expected.chooser, result.chooser) assert.Equal(t, expected.metrics, result.metrics) assert.Equal(t, expected.pbsAnalytics, result.pbsAnalytics) assert.Equal(t, expected.accountsFetcher, result.accountsFetcher) @@ -103,10 +105,10 @@ func TestCookieSyncHandle(t *testing.T) { syncTypeExpected := []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} sync := usersync.Sync{URL: "aURL", Type: usersync.SyncTypeRedirect, SupportCORS: true} syncer := MockSyncer{} - syncer.On("GetSync", syncTypeExpected, privacy.Policies{}).Return(sync, nil).Maybe() + syncer.On("GetSync", syncTypeExpected, macros.UserSyncPrivacy{}).Return(sync, nil).Maybe() cookieWithSyncs := usersync.NewCookie() - cookieWithSyncs.TrySync("foo", "anyID") + cookieWithSyncs.Sync("foo", "anyID") testCases := []struct { description string @@ -116,7 +118,7 @@ func TestCookieSyncHandle(t *testing.T) { expectedStatusCode int expectedBody string setMetricsExpectations func(*metrics.MetricsEngineMock) - setAnalyticsExpectations func(*MockAnalytics) + setAnalyticsExpectations func(*MockAnalyticsRunner) }{ { description: "Request With Cookie", @@ -135,7 +137,7 @@ func TestCookieSyncHandle(t *testing.T) { m.On("RecordCookieSync", metrics.CookieSyncOK).Once() m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -167,7 +169,7 @@ func TestCookieSyncHandle(t *testing.T) { m.On("RecordCookieSync", metrics.CookieSyncOK).Once() m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -192,14 +194,14 @@ func TestCookieSyncHandle(t *testing.T) { SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, }, expectedStatusCode: 400, - expectedBody: `JSON parsing failed: invalid character 'm' looking for beginning of value` + "\n", + expectedBody: `JSON parsing failed: expect { or n, but found m` + "\n", setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 400, - Errors: []error{errors.New("JSON parsing failed: invalid character 'm' looking for beginning of value")}, + Errors: []error{errors.New("JSON parsing failed: expect { or n, but found m")}, BidderStatus: []*analytics.CookieSyncBidder{}, } a.On("LogCookieSyncObject", &expected).Once() @@ -219,7 +221,7 @@ func TestCookieSyncHandle(t *testing.T) { setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncOptOut).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 401, Errors: []error{errors.New("User has opted out")}, @@ -233,7 +235,7 @@ func TestCookieSyncHandle(t *testing.T) { givenCookie: cookieWithSyncs, givenBody: strings.NewReader(`{}`), givenChooserResult: usersync.Result{ - Status: usersync.StatusBlockedByGDPR, + Status: usersync.StatusBlockedByPrivacy, BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, }, @@ -242,7 +244,7 @@ func TestCookieSyncHandle(t *testing.T) { setMetricsExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordCookieSync", metrics.CookieSyncGDPRHostCookieBlocked).Once() }, - setAnalyticsExpectations: func(a *MockAnalytics) { + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { expected := analytics.CookieSyncObject{ Status: 200, Errors: nil, @@ -251,13 +253,45 @@ func TestCookieSyncHandle(t *testing.T) { a.On("LogCookieSyncObject", &expected).Once() }, }, + { + description: "Debug Check", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{"debug": true}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"ok","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `],"debug":[{"bidder":"a","error":"Already in sync"}]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, } for _, test := range testCases { mockMetrics := metrics.MetricsEngineMock{} test.setMetricsExpectations(&mockMetrics) - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} test.setAnalyticsExpectations(&mockAnalytics) fakeAccountFetcher := FakeAccountsFetcher{} @@ -271,7 +305,9 @@ func TestCookieSyncHandle(t *testing.T) { request := httptest.NewRequest("POST", "/cookiesync", test.givenBody) if test.givenCookie != nil { - request.AddCookie(test.givenCookie.ToHTTPCookie(24 * time.Hour)) + httpCookie, err := ToHTTPCookie(test.givenCookie) + assert.NoError(t, err) + request.AddCookie(httpCookie) } writer := httptest.NewRecorder() @@ -365,7 +401,7 @@ func TestExtractGDPRSignal(t *testing.T) { expected: testOutput{ gdprSignal: gdpr.SignalAmbiguous, gdprString: "2", - err: &errortypes.BadInput{"GDPR signal should be integer 0 or 1"}, + err: &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"}, }, }, { @@ -397,8 +433,9 @@ func TestExtractPrivacyPolicies(t *testing.T) { usersyncDefaultGDPRValue string } type testOutput struct { - policies privacy.Policies + macros macros.UserSyncPrivacy gdprSignal gdpr.Signal + policies privacy.Policies err error } testCases := []struct { @@ -412,8 +449,9 @@ func TestExtractPrivacyPolicies(t *testing.T) { request: cookieSyncRequest{GPP: "malformedGPPString"}, }, expected: testOutput{ - policies: privacy.Policies{}, + macros: macros.UserSyncPrivacy{}, gdprSignal: gdpr.SignalNo, + policies: privacy.Policies{}, err: errors.New("error parsing GPP header, header must have type=3"), }, }, @@ -422,14 +460,15 @@ func TestExtractPrivacyPolicies(t *testing.T) { in: testInput{ request: cookieSyncRequest{ GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", - GPPSid: "malformed", + GPPSID: "malformed", USPrivacy: "1YYY", }, }, expected: testOutput{ - policies: privacy.Policies{}, + macros: macros.UserSyncPrivacy{}, gdprSignal: gdpr.SignalNo, - err: &strconv.NumError{"ParseInt", "malformed", strconv.ErrSyntax}, + policies: privacy.Policies{}, + err: &strconv.NumError{Func: "ParseInt", Num: "malformed", Err: strconv.ErrSyntax}, }, }, { @@ -437,13 +476,14 @@ func TestExtractPrivacyPolicies(t *testing.T) { in: testInput{ request: cookieSyncRequest{ GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", - GPPSid: "6", + GPPSID: "6", USPrivacy: "1YYY", }, }, expected: testOutput{ - policies: privacy.Policies{}, + macros: macros.UserSyncPrivacy{}, gdprSignal: gdpr.SignalNo, + policies: privacy.Policies{}, err: errors.New("request.us_privacy consent does not match uspv1"), }, }, @@ -452,40 +492,36 @@ func TestExtractPrivacyPolicies(t *testing.T) { in: testInput{ request: cookieSyncRequest{ GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", - GPPSid: "6", + GPPSID: "6", }, }, expected: testOutput{ - policies: privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: "0", - Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", - }, - CCPA: ccpa.Policy{ - Consent: "1YNN", - }, - GPP: gppPrivacy.Policy{ - Consent: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", - RawSID: "6", - }, + macros: macros.UserSyncPrivacy{ + GDPR: "0", + GDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + USPrivacy: "1YNN", + GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + GPPSID: "6", }, gdprSignal: gdpr.SignalNo, + policies: privacy.Policies{GPPSID: []int8{6}}, err: nil, }, }, } for _, tc := range testCases { - // run - outPolicies, outSignal, outErr := extractPrivacyPolicies(tc.in.request, tc.in.usersyncDefaultGDPRValue) - // assertions - assert.Equal(t, tc.expected.policies, outPolicies, tc.desc) + outMacros, outSignal, outPolicies, outErr := extractPrivacyPolicies(tc.in.request, tc.in.usersyncDefaultGDPRValue) + + assert.Equal(t, tc.expected.macros, outMacros, tc.desc) assert.Equal(t, tc.expected.gdprSignal, outSignal, tc.desc) + assert.Equal(t, tc.expected.policies, outPolicies, tc.desc) assert.Equal(t, tc.expected.err, outErr, tc.desc) } } func TestCookieSyncParseRequest(t *testing.T) { expectedCCPAParsedPolicy, _ := ccpa.Policy{Consent: "1NYN"}.Parse(map[string]struct{}{}) + emptyActivityPoliciesRequest := privacy.NewRequestFromPolicies(privacy.Policies{}) testCases := []struct { description string @@ -495,9 +531,10 @@ func TestCookieSyncParseRequest(t *testing.T) { givenCCPAEnabled bool givenAccountRequired bool expectedError string - expectedPrivacy privacy.Policies + expectedPrivacy macros.UserSyncPrivacy expectedRequest usersync.Request }{ + { description: "Complete Request - includes GPP string with EU TCF V2", givenBody: strings.NewReader(`{` + @@ -514,23 +551,17 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: "1", - Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - GPP: gppPrivacy.Policy{ - Consent: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", - RawSID: "2", - }, + expectedPrivacy: macros.UserSyncPrivacy{ + GDPR: "1", + GDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + USPrivacy: "1NYN", + GPP: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GPPSID: "2", }, expectedRequest: usersync.Request{ Bidders: []string{"a", "b"}, @@ -542,11 +573,14 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, + activityRequest: privacy.NewRequestFromPolicies(privacy.Policies{GPPSID: []int8{2}}), + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), }, + GPPSID: "2", }, }, { @@ -561,19 +595,15 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: "1", - Consent: "anyGDPRConsent", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, + expectedPrivacy: macros.UserSyncPrivacy{ + GDPR: "1", + GDPRConsent: "anyGDPRConsent", + USPrivacy: "1NYN", }, expectedRequest: usersync.Request{ Bidders: []string{"a", "b"}, @@ -585,6 +615,8 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -597,10 +629,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`{}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -614,12 +648,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: true, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: true, @@ -627,6 +661,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -640,12 +676,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: false, @@ -653,6 +689,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -666,12 +704,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: true, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: false, @@ -679,6 +717,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -692,12 +732,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: false, @@ -705,6 +745,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -718,12 +760,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: true, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: true, @@ -731,6 +773,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -744,12 +788,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Cooperative: usersync.Cooperative{ Enabled: true, @@ -757,6 +801,8 @@ func TestCookieSyncParseRequest(t *testing.T) { }, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -769,10 +815,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`{"us_privacy":"invalid"}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -785,13 +833,14 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`{"us_privacy":"1NYN"}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: false, - expectedPrivacy: privacy.Policies{ - CCPA: ccpa.Policy{ - Consent: "1NYN"}, + expectedPrivacy: macros.UserSyncPrivacy{ + USPrivacy: "1NYN", }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -804,7 +853,7 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`malformed`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedError: "JSON parsing failed: invalid character 'm' looking for beginning of value", + expectedError: "JSON parsing failed: expect { or n, but found m", }, { description: "Invalid Type Filter", @@ -825,12 +874,14 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`{"gdpr":0}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedPrivacy: privacy.Policies{ - GDPR: gdprPrivacy.Policy{Signal: "0"}, + expectedPrivacy: macros.UserSyncPrivacy{ + GDPR: "0", }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -850,12 +901,14 @@ func TestCookieSyncParseRequest(t *testing.T) { givenBody: strings.NewReader(`{}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, - expectedPrivacy: privacy.Policies{ - GDPR: gdprPrivacy.Policy{Signal: ""}, + expectedPrivacy: macros.UserSyncPrivacy{ + GDPR: "", }, expectedRequest: usersync.Request{ Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -887,11 +940,10 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ - Cooperative: config.UserSyncCooperative{ - PriorityGroups: [][]string{{"a", "b", "c"}}, - }, + PriorityGroups: [][]string{{"a", "b", "c"}}, + Cooperative: config.UserSyncCooperative{}, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Bidders: []string{"a", "b"}, Cooperative: usersync.Cooperative{ @@ -901,6 +953,8 @@ func TestCookieSyncParseRequest(t *testing.T) { Limit: 30, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -917,12 +971,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Bidders: []string{"a", "b"}, Cooperative: usersync.Cooperative{ @@ -932,6 +986,8 @@ func TestCookieSyncParseRequest(t *testing.T) { Limit: 20, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -948,12 +1004,12 @@ func TestCookieSyncParseRequest(t *testing.T) { givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, Cooperative: config.UserSyncCooperative{ EnabledByDefault: false, - PriorityGroups: [][]string{{"a", "b", "c"}}, }, }, - expectedPrivacy: privacy.Policies{}, + expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ Bidders: []string{"a", "b"}, Cooperative: usersync.Cooperative{ @@ -963,6 +1019,8 @@ func TestCookieSyncParseRequest(t *testing.T) { Limit: 20, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -996,8 +1054,9 @@ func TestCookieSyncParseRequest(t *testing.T) { ccpaEnforce: test.givenCCPAEnabled, }, accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ - "TestAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`), - "DisabledAccount": json.RawMessage(`{"disabled":true}`), + "TestAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`), + "DisabledAccount": json.RawMessage(`{"disabled":true}`), + "ValidAccountInvalidActivities": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`), }}, } assert.NoError(t, endpoint.config.MarshalAccountDefaults()) @@ -1242,7 +1301,7 @@ func TestSetCooperativeSync(t *testing.T) { func TestWriteParseRequestErrorMetrics(t *testing.T) { err := errors.New("anyError") - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", mock.Anything) writer := httptest.NewRecorder() @@ -1465,7 +1524,7 @@ func TestParseBidderFilter(t *testing.T) { func TestCookieSyncHandleError(t *testing.T) { err := errors.New("anyError") - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", mock.Anything) writer := httptest.NewRecorder() @@ -1502,14 +1561,14 @@ func TestCookieSyncWriteBidderMetrics(t *testing.T) { }, { description: "One - Blocked By GDPR", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByGDPR}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, }, { description: "One - Blocked By CCPA", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByCCPA}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, @@ -1558,26 +1617,38 @@ func TestCookieSyncHandleResponse(t *testing.T) { Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), } syncTypeExpected := []usersync.SyncType{usersync.SyncTypeRedirect} - privacyPolicies := privacy.Policies{CCPA: ccpa.Policy{Consent: "anyConsent"}} + privacyMacros := macros.UserSyncPrivacy{USPrivacy: "anyConsent"} // The & in the URL is necessary to test proper JSON encoding. syncA := usersync.Sync{URL: "https://syncA.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: true} syncerA := MockSyncer{} - syncerA.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncA, nil).Maybe() + syncerA.On("GetSync", syncTypeExpected, privacyMacros).Return(syncA, nil).Maybe() // The & in the URL is necessary to test proper JSON encoding. syncB := usersync.Sync{URL: "https://syncB.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: false} syncerB := MockSyncer{} - syncerB.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncB, nil).Maybe() + syncerB.On("GetSync", syncTypeExpected, privacyMacros).Return(syncB, nil).Maybe() syncWithError := usersync.Sync{} syncerWithError := MockSyncer{} - syncerWithError.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncWithError, errors.New("anyError")).Maybe() + syncerWithError.On("GetSync", syncTypeExpected, privacyMacros).Return(syncWithError, errors.New("anyError")).Maybe() + + bidderEvalForDebug := []usersync.BidderEvaluation{ + {Bidder: "Bidder1", Status: usersync.StatusAlreadySynced}, + {Bidder: "Bidder2", Status: usersync.StatusUnknownBidder}, + {Bidder: "Bidder3", Status: usersync.StatusUnconfiguredBidder}, + {Bidder: "Bidder4", Status: usersync.StatusBlockedByPrivacy}, + {Bidder: "Bidder5", Status: usersync.StatusTypeNotSupported}, + {Bidder: "Bidder6", Status: usersync.StatusBlockedByUserOptOut}, + {Bidder: "Bidder7", Status: usersync.StatusBlockedByDisabledUsersync}, + {Bidder: "BidderA", Status: usersync.StatusDuplicate, SyncerKey: "syncerB"}, + } testCases := []struct { description string givenCookieHasSyncs bool givenSyncersChosen []usersync.SyncerChoice + givenDebug bool expectedJSON string expectedAnalytics analytics.CookieSyncObject }{ @@ -1655,22 +1726,37 @@ func TestCookieSyncHandleResponse(t *testing.T) { expectedJSON: `{"status":"no_cookie","bidder_status":[]}` + "\n", expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, }, + { + description: "Debug is true, should see all rejected bidder eval statuses in response", + givenCookieHasSyncs: true, + givenDebug: true, + givenSyncersChosen: []usersync.SyncerChoice{}, + expectedJSON: `{"status":"ok","bidder_status":[],"debug":[{"bidder":"Bidder1","error":"Already in sync"},{"bidder":"Bidder2","error":"Unsupported bidder"},{"bidder":"Bidder3","error":"No sync config"},{"bidder":"Bidder4","error":"Rejected by privacy"},{"bidder":"Bidder5","error":"Type not supported"},{"bidder":"Bidder6","error":"Status blocked by user opt out"},{"bidder":"Bidder7","error":"Sync disabled by config"},{"bidder":"BidderA","error":"Duplicate bidder synced as syncerB"}]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, + }, } for _, test := range testCases { - mockAnalytics := MockAnalytics{} + mockAnalytics := MockAnalyticsRunner{} mockAnalytics.On("LogCookieSyncObject", &test.expectedAnalytics).Once() cookie := usersync.NewCookie() if test.givenCookieHasSyncs { - if err := cookie.TrySync("foo", "anyID"); err != nil { + if err := cookie.Sync("foo", "anyID"); err != nil { assert.FailNow(t, test.description+":set_cookie") } } writer := httptest.NewRecorder() endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} - endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyPolicies, test.givenSyncersChosen) + + var bidderEval []usersync.BidderEvaluation + if test.givenDebug { + bidderEval = bidderEvalForDebug + } else { + bidderEval = []usersync.BidderEvaluation{} + } + endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen, bidderEval, test.givenDebug) if assert.Equal(t, writer.Code, http.StatusOK, test.description+":http_status") { assert.Equal(t, writer.Header().Get("Content-Type"), "application/json; charset=utf-8", test.description+":http_header") @@ -1870,6 +1956,78 @@ func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) { } } +func TestCookieSyncActivityControlIntegration(t *testing.T) { + testCases := []struct { + name string + bidderName string + accountPrivacy *config.AccountPrivacy + expectedResult bool + }{ + { + name: "activity_is_allowed", + bidderName: "bidderA", + accountPrivacy: getDefaultActivityConfig("bidderA", true), + expectedResult: true, + }, + { + name: "activity_is_denied", + bidderName: "bidderA", + accountPrivacy: getDefaultActivityConfig("bidderA", false), + expectedResult: false, + }, + { + name: "activity_is_abstain", + bidderName: "bidderA", + accountPrivacy: nil, + expectedResult: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + activities := privacy.NewActivityControl(test.accountPrivacy) + up := usersyncPrivacy{ + activityControl: activities, + } + actualResult := up.ActivityAllowsUserSync(test.bidderName) + assert.Equal(t, test.expectedResult, actualResult) + }) + } +} + +func TestUsersyncPrivacyGDPRInScope(t *testing.T) { + testCases := []struct { + description string + givenGdprSignal gdpr.Signal + expected bool + }{ + { + description: "GDPR Signal Yes", + givenGdprSignal: gdpr.SignalYes, + expected: true, + }, + { + description: "GDPR Signal No", + givenGdprSignal: gdpr.SignalNo, + expected: false, + }, + { + description: "GDPR Signal Ambigious", + givenGdprSignal: gdpr.SignalAmbiguous, + expected: false, + }, + } + + for _, test := range testCases { + privacy := usersyncPrivacy{ + gdprSignal: test.givenGdprSignal, + } + + result := privacy.GDPRInScope() + assert.Equal(t, test.expected, result, test.description) + } +} + func TestCombineErrors(t *testing.T) { testCases := []struct { description string @@ -1893,7 +2051,7 @@ func TestCombineErrors(t *testing.T) { }, { description: "Special Case: blocked (rejected via block list)", - givenErrorList: []error{&errortypes.BlacklistedAcct{}}, + givenErrorList: []error{&errortypes.AccountDisabled{}}, expectedError: errCookieSyncAccountBlocked, }, { @@ -1908,7 +2066,7 @@ func TestCombineErrors(t *testing.T) { }, { description: "Special Case: multiple special cases, first one wins", - givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, + givenErrorList: []error{&errortypes.AccountDisabled{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, expectedError: errCookieSyncAccountBlocked, }, } @@ -1936,7 +2094,7 @@ func (m *MockSyncer) Key() string { return args.String(0) } -func (m *MockSyncer) DefaultSyncType() usersync.SyncType { +func (m *MockSyncer) DefaultResponseFormat() usersync.SyncType { args := m.Called() return args.Get(0).(usersync.SyncType) } @@ -1946,37 +2104,37 @@ func (m *MockSyncer) SupportsType(syncTypes []usersync.SyncType) bool { return args.Bool(0) } -func (m *MockSyncer) GetSync(syncTypes []usersync.SyncType, privacyPolicies privacy.Policies) (usersync.Sync, error) { - args := m.Called(syncTypes, privacyPolicies) +func (m *MockSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros.UserSyncPrivacy) (usersync.Sync, error) { + args := m.Called(syncTypes, privacyMacros) return args.Get(0).(usersync.Sync), args.Error(1) } -type MockAnalytics struct { +type MockAnalyticsRunner struct { mock.Mock } -func (m *MockAnalytics) LogAuctionObject(obj *analytics.AuctionObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogAuctionObject(obj *analytics.AuctionObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogVideoObject(obj *analytics.VideoObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogVideoObject(obj *analytics.VideoObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogCookieSyncObject(obj *analytics.CookieSyncObject) { +func (m *MockAnalyticsRunner) LogCookieSyncObject(obj *analytics.CookieSyncObject) { m.Called(obj) } -func (m *MockAnalytics) LogSetUIDObject(obj *analytics.SetUIDObject) { +func (m *MockAnalyticsRunner) LogSetUIDObject(obj *analytics.SetUIDObject) { m.Called(obj) } -func (m *MockAnalytics) LogAmpObject(obj *analytics.AmpObject) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogAmpObject(obj *analytics.AmpObject, ac privacy.ActivityControl) { + m.Called(obj, ac) } -func (m *MockAnalytics) LogNotificationEventObject(obj *analytics.NotificationEvent) { - m.Called(obj) +func (m *MockAnalyticsRunner) LogNotificationEventObject(obj *analytics.NotificationEvent, ac privacy.ActivityControl) { + m.Called(obj, ac) } type MockGDPRPerms struct { @@ -2030,3 +2188,22 @@ func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCo AllowBidRequest: true, }, nil } + +func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + SyncUser: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"bidder"}, + }, + }, + }, + }, + }, + } +} diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index d35cb74cea4..f08154471fe 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -1,12 +1,12 @@ package endpoints import ( - "encoding/json" "net/http" "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // currencyRatesInfo holds currency rates information. @@ -60,7 +60,7 @@ func NewCurrencyRatesEndpoint(rateConverter rateConverter, fetchingInterval time currencyRateInfo := newCurrencyRatesInfo(rateConverter, fetchingInterval) return func(w http.ResponseWriter, _ *http.Request) { - jsonOutput, err := json.Marshal(currencyRateInfo) + jsonOutput, err := jsonutil.Marshal(currencyRateInfo) if err != nil { glog.Errorf("/currency/rates Critical error when trying to marshal currencyRateInfo: %v", err) w.WriteHeader(http.StatusInternalServerError) diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go index 7fc513e7dbe..0b953c640e2 100644 --- a/endpoints/currency_rates_test.go +++ b/endpoints/currency_rates_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/v2/currency" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index 7efb8af782b..d19a3912f59 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -2,7 +2,6 @@ package events import ( "errors" - "fmt" "io" "net/http" "net/http/httptest" @@ -10,24 +9,23 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHandleAccountServiceErrors(t *testing.T) { tests := map[string]struct { - fetcher *mockAccountsFetcher - cfg *config.Configuration - accountID string - want struct { - code int - response string - } + fetcher *mockAccountsFetcher + cfg *config.Configuration + accountID string + wantCode int + wantResponse string }{ - "badRequest": { + "bad-request": { fetcher: &mockAccountsFetcher{ Fail: true, Error: errors.New("some error"), @@ -40,16 +38,11 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 400, - response: "Invalid request: some error\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", - }, + accountID: "testacc", + wantCode: 400, + wantResponse: "Invalid request: some error\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", }, - "malformedAccountConfig": { + "malformed-account-config": { fetcher: &mockAccountsFetcher{ Fail: true, Error: &errortypes.MalformedAcct{}, @@ -60,34 +53,25 @@ func TestHandleAccountServiceErrors(t *testing.T) { TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "malformed_acct", - want: struct { - code int - response string - }{ - code: 500, - response: "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n", - }, + accountID: "malformed_acct", + wantCode: 500, + wantResponse: "Invalid request: The prebid-server account config for account id \"malformed_acct\" is malformed. Please reach out to the prebid server host.\n", }, - "serviceUnavailable": { + "service-unavailable": { fetcher: &mockAccountsFetcher{ Fail: false, }, cfg: &config.Configuration{ - BlacklistedAcctMap: map[string]bool{"testacc": true}, - MaxRequestSize: maxSize, + AccountDefaults: config.Account{}, + AccountRequired: true, + MaxRequestSize: maxSize, VTrack: config.VTrack{ TimeoutMS: int64(2000), AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 503, - response: "Invalid request: Prebid-server has disabled Account ID: testacc, please reach out to the prebid server host.\n", - }, + accountID: "disabled_acct", + wantCode: 503, + wantResponse: "Invalid request: Prebid-server has disabled Account ID: disabled_acct, please reach out to the prebid server host.\n", }, "timeout": { fetcher: &mockAccountsFetcher{ @@ -106,19 +90,13 @@ func TestHandleAccountServiceErrors(t *testing.T) { AllowUnknownBidder: false, }, }, - accountID: "testacc", - want: struct { - code int - response string - }{ - code: 504, - response: "Invalid request: context deadline exceeded\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", - }, + accountID: "testacc", + wantCode: 504, + wantResponse: "Invalid request: context deadline exceeded\nInvalid request: Prebid-server could not verify the Account ID. Please reach out to the prebid server host.\n", }, } for name, test := range tests { - handlers := []struct { name string h httprouter.Handle @@ -137,13 +115,11 @@ func TestHandleAccountServiceErrors(t *testing.T) { // execute handler.h(recorder, handler.r, nil) d, err := io.ReadAll(recorder.Result().Body) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // validate - assert.Equal(t, test.want.code, recorder.Result().StatusCode, fmt.Sprintf("Expected %d", test.want.code)) - assert.Equal(t, test.want.response, string(d)) + assert.Equal(t, test.wantCode, recorder.Result().StatusCode) + assert.Equal(t, test.wantResponse, string(d)) }) } } diff --git a/endpoints/events/event.go b/endpoints/events/event.go index 089d5606552..b92b72f17ad 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -10,14 +10,17 @@ import ( "time" "unicode" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/httputil" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/httputil" ) const ( @@ -40,13 +43,13 @@ const integrationParamMaxLength = 64 type eventEndpoint struct { Accounts stored_requests.AccountFetcher - Analytics analytics.PBSAnalyticsModule + Analytics analytics.Runner Cfg *config.Configuration TrackingPixel *httputil.Pixel MetricsEngine metrics.MetricsEngine } -func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, analytics analytics.PBSAnalyticsModule, me metrics.MetricsEngine) httprouter.Handle { +func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, analytics analytics.Runner, me metrics.MetricsEngine) httprouter.Handle { ee := &eventEndpoint{ Accounts: accounts, Analytics: analytics, @@ -108,17 +111,19 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou } // Check if events are enabled for the account - if !account.Events.IsEnabled() { + if !account.Events.Enabled { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(fmt.Sprintf("Account '%s' doesn't support events", eventRequest.AccountID))) return } + activities := privacy.NewActivityControl(&account.Privacy) + // handle notification event e.Analytics.LogNotificationEventObject(&analytics.NotificationEvent{ Request: eventRequest, Account: account, - }) + }, activities) // Add tracking pixel if format == image if eventRequest.Format == analytics.Image { @@ -185,7 +190,12 @@ func ParseEventRequest(r *http.Request) (*analytics.EventRequest, []error) { } // Bidder - event.Bidder = r.URL.Query().Get(BidderParameter) + bidderName := r.URL.Query().Get(BidderParameter) + if normalisedBidderName, ok := openrtb_ext.NormalizeBidderName(bidderName); ok { + bidderName = normalisedBidderName.String() + } + + event.Bidder = bidderName return event, errs } @@ -206,7 +216,7 @@ func HandleAccountServiceErrors(errs []error) (status int, messages []string) { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable } if errCode == errortypes.MalformedAcctErrorCode { diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index 95711ba1328..81d000fd8a4 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -12,70 +12,63 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" "github.com/stretchr/testify/assert" ) -// Mock Analytics Module type eventsMockAnalyticsModule struct { Fail bool Error error Invoked bool } -func (e *eventsMockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject) { +func (e *eventsMockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } - return } -func (e *eventsMockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject) { +func (e *eventsMockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { if e.Fail { panic(e.Error) } - return } func (e *eventsMockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) { if e.Fail { panic(e.Error) } - return } -func (e *eventsMockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) { +func (e *eventsMockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } - return } -func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { +func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) { if e.Fail { panic(e.Error) } e.Invoked = true - - return } -// Mock Account fetcher var mockAccountData = map[string]json.RawMessage{ "events_enabled": json.RawMessage(`{"events": {"enabled":true}}`), "events_disabled": json.RawMessage(`{"events": {"enabled":false}}`), "malformed_acct": json.RawMessage(`{"events": {"enabled":"invalid type"}}`), + "disabled_acct": json.RawMessage(`{"disabled": true}`), } type mockAccountsFetcher struct { @@ -102,7 +95,7 @@ func (maf mockAccountsFetcher) FetchAccount(ctx context.Context, defaultAccountJ return nil, []error{maf.Error} } - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} } // Tests @@ -658,6 +651,19 @@ func TestShouldParseEventCorrectly(t *testing.T) { Analytics: analytics.Enabled, }, }, + "case insensitive bidder name": { + req: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=RubiCon&int=intType", strings.NewReader("")), + expected: &analytics.EventRequest{ + Type: analytics.Win, + BidID: "bidId", + Timestamp: 1000, + Bidder: "rubicon", + AccountID: "", + Format: analytics.Blank, + Analytics: analytics.Enabled, + Integration: "intType", + }, + }, } for name, test := range tests { diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index d320fdc6989..5d794651ba4 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -11,13 +11,15 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( @@ -27,12 +29,15 @@ const ( ImpressionOpenTag = "" ) +type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) + type vtrackEndpoint struct { - Cfg *config.Configuration - Accounts stored_requests.AccountFetcher - BidderInfos config.BidderInfos - Cache prebid_cache_client.Client - MetricsEngine metrics.MetricsEngine + Cfg *config.Configuration + Accounts stored_requests.AccountFetcher + BidderInfos config.BidderInfos + Cache prebid_cache_client.Client + MetricsEngine metrics.MetricsEngine + normalizeBidderName normalizeBidderName } type BidCacheRequest struct { @@ -49,11 +54,12 @@ type CacheObject struct { func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos config.BidderInfos, me metrics.MetricsEngine) httprouter.Handle { vte := &vtrackEndpoint{ - Cfg: cfg, - Accounts: accounts, - BidderInfos: bidderInfos, - Cache: cache, - MetricsEngine: me, + Cfg: cfg, + Accounts: accounts, + BidderInfos: bidderInfos, + Cache: cache, + MetricsEngine: me, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } return vte.Handle @@ -118,7 +124,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro } } - d, err := json.Marshal(*cachingResponse) + d, err := jsonutil.Marshal(*cachingResponse) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -182,7 +188,7 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B return req, err } - if err := json.Unmarshal(requestJson, req); err != nil { + if err := jsonutil.UnmarshalValid(requestJson, req); err != nil { return req, err } @@ -203,7 +209,7 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B // handleVTrackRequest handles a VTrack request func (v *vtrackEndpoint) handleVTrackRequest(ctx context.Context, req *BidCacheRequest, account *config.Account, integration string) (*BidCacheResponse, []error) { - biddersAllowingVastUpdate := getBiddersAllowingVastUpdate(req, &v.BidderInfos, v.Cfg.VTrack.AllowUnknownBidder) + biddersAllowingVastUpdate := getBiddersAllowingVastUpdate(req, &v.BidderInfos, v.Cfg.VTrack.AllowUnknownBidder, v.normalizeBidderName) // cache data r, errs := v.cachePutObjects(ctx, req, biddersAllowingVastUpdate, account.ID, integration) @@ -251,11 +257,11 @@ func (v *vtrackEndpoint) cachePutObjects(ctx context.Context, req *BidCacheReque } // getBiddersAllowingVastUpdate returns a list of bidders that allow VAST XML modification -func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool) map[string]struct{} { +func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) map[string]struct{} { bl := map[string]struct{}{} for _, bcr := range req.Puts { - if _, ok := bl[bcr.Bidder]; isAllowVastForBidder(bcr.Bidder, bidderInfos, allowUnknownBidder) && !ok { + if _, ok := bl[bcr.Bidder]; isAllowVastForBidder(bcr.Bidder, bidderInfos, allowUnknownBidder, normalizeBidderName) && !ok { bl[bcr.Bidder] = struct{}{} } } @@ -264,12 +270,14 @@ func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.Bidd } // isAllowVastForBidder checks if a bidder is active and allowed to modify vast xml data -func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool) bool { +func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) bool { //if bidder is active and isModifyingVastXmlAllowed is true // check if bidder is configured - if b, ok := (*bidderInfos)[bidder]; bidderInfos != nil && ok { - // check if bidder is enabled - return b.IsEnabled() && b.ModifyingVastXmlAllowed + if normalizedBidder, ok := normalizeBidderName(bidder); ok { + if b, ok := (*bidderInfos)[normalizedBidder.String()]; bidderInfos != nil && ok { + // check if bidder is enabled + return b.IsEnabled() && b.ModifyingVastXmlAllowed + } } return allowUnknownBidder @@ -312,7 +320,7 @@ func ModifyVastXmlString(externalUrl, vast, bidid, bidder, accountID string, tim // ModifyVastXmlJSON modifies BidCacheRequest element Vast XML data func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, accountId string, timestamp int64, integrationType string) json.RawMessage { var vast string - if err := json.Unmarshal(data, &vast); err != nil { + if err := jsonutil.Unmarshal(data, &vast); err != nil { // failed to decode json, fall back to string vast = string(data) } diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 2c40c9b41de..3ccfeacf82f 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -12,9 +12,11 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -28,15 +30,17 @@ const ( // Mock pbs cache client type vtrackMockCacheClient struct { - Fail bool - Error error - Uuids []string + Fail bool + Error error + Uuids []string + Values []prebid_cache_client.Cacheable } func (m *vtrackMockCacheClient) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) { if m.Fail { return []string{}, []error{m.Error} } + m.Values = values return m.Uuids, []error{} } func (m *vtrackMockCacheClient) GetExtCacheData() (scheme string, host string, path string) { @@ -64,10 +68,11 @@ func TestShouldRespondWithBadRequestWhenAccountParameterIsMissing(t *testing.T) recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -105,10 +110,11 @@ func TestShouldRespondWithBadRequestWhenRequestBodyIsEmpty(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -146,10 +152,11 @@ func TestShouldRespondWithBadRequestWhenRequestBodyIsInvalid(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -180,7 +187,7 @@ func TestShouldRespondWithBadRequestWhenBidIdIsMissing(t *testing.T) { }, } - reqData, err := json.Marshal(data) + reqData, err := jsonutil.Marshal(data) if err != nil { t.Fatal(err) } @@ -190,10 +197,11 @@ func TestShouldRespondWithBadRequestWhenBidIdIsMissing(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -232,7 +240,7 @@ func TestShouldRespondWithBadRequestWhenBidderIsMissing(t *testing.T) { }, } - reqData, err := json.Marshal(data) + reqData, err := jsonutil.Marshal(data) if err != nil { t.Fatal(err) } @@ -242,10 +250,11 @@ func TestShouldRespondWithBadRequestWhenBidderIsMissing(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -291,10 +300,11 @@ func TestShouldRespondWithInternalServerErrorWhenPbsCacheClientFails(t *testing. recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -340,10 +350,11 @@ func TestShouldTolerateAccountNotFound(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -397,10 +408,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowe recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -459,11 +471,15 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t recorder := httptest.NewRecorder() + var mockNormalizeBidderName normalizeBidderName = func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: mockNormalizeBidderName, } // execute @@ -478,6 +494,95 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t assert.Equal(t, 200, recorder.Result().StatusCode, "Expected 200 when account is not found and request is valid") assert.Equal(t, "{\"responses\":[{\"uuid\":\"uuid1\"},{\"uuid\":\"uuid2\"}]}", string(d), "Expected 200 when account is found and request is valid") assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.Len(t, mockCacheClient.Values, 2) + assert.Contains(t, string(mockCacheClient.Values[0].Data), "bidder=bidder") + assert.Contains(t, string(mockCacheClient.Values[1].Data), "bidder=updatable_bidder") +} + +func TestShouldSendToCacheExpectedPutsAndUpdatableCaseSensitiveBiddersWhenBidderVastAllowed(t *testing.T) { + // mock pbs cache client + mockCacheClient := &vtrackMockCacheClient{ + Fail: false, + Uuids: []string{"uuid1", "uuid2"}, + } + + // mock AccountsFetcher + mockAccountsFetcher := &mockAccountsFetcher{ + Fail: false, + } + + // config + cfg := &config.Configuration{ + MaxRequestSize: maxSize, VTrack: config.VTrack{ + TimeoutMS: int64(2000), AllowUnknownBidder: false, + }, + AccountDefaults: config.Account{}, + } + cfg.MarshalAccountDefaults() + + // bidder info + bidderInfos := make(config.BidderInfos) + bidderInfos["appnexus"] = config.BidderInfo{ + Disabled: false, + ModifyingVastXmlAllowed: true, + } + + d, err := getVTrackRequestData(true, true) + assert.NoError(t, err) + + cacheReq := &BidCacheRequest{ + Puts: []prebid_cache_client.Cacheable{ + { + Type: prebid_cache_client.TypeXML, + BidID: "bidId1", + Bidder: "APPNEXUS", // case sensitive name + Data: d, + TTLSeconds: 3600, + Timestamp: 1000, + }, + { + Type: prebid_cache_client.TypeXML, + BidID: "bidId2", + Bidder: "ApPnExUs", // case sensitive name + Data: d, + TTLSeconds: 3600, + Timestamp: 1000, + }, + }, + } + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err = enc.Encode(cacheReq) + assert.NoError(t, err) + data := buf.String() + + req := httptest.NewRequest("POST", "/vtrack?a=events_enabled", strings.NewReader(data)) + + recorder := httptest.NewRecorder() + e := vtrackEndpoint{ + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, + } + + // execute + e.Handle(recorder, req, nil) + + d, err = io.ReadAll(recorder.Result().Body) + if err != nil { + t.Fatal(err) + } + + // validate + assert.Equal(t, 200, recorder.Result().StatusCode, "Expected 200 when account is not found and request is valid") + assert.Equal(t, "{\"responses\":[{\"uuid\":\"uuid1\"},{\"uuid\":\"uuid2\"}]}", string(d), "Expected 200 when account is found and request is valid") + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.Len(t, mockCacheClient.Values, 2) + assert.Contains(t, string(mockCacheClient.Values[0].Data), "bidder=APPNEXUS") + assert.Contains(t, string(mockCacheClient.Values[1].Data), "bidder=ApPnExUs") } func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidderIsAllowed(t *testing.T) { @@ -515,10 +620,11 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidde recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -571,10 +677,11 @@ func TestShouldReturnBadRequestWhenRequestExceedsMaxRequestSize(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: bidderInfos, - Cache: mockCacheClient, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: bidderInfos, + Cache: mockCacheClient, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute @@ -615,10 +722,11 @@ func TestShouldRespondWithInternalErrorPbsCacheIsNotConfigured(t *testing.T) { recorder := httptest.NewRecorder() e := vtrackEndpoint{ - Cfg: cfg, - BidderInfos: nil, - Cache: nil, - Accounts: mockAccountsFetcher, + Cfg: cfg, + BidderInfos: nil, + Cache: nil, + Accounts: mockAccountsFetcher, + normalizeBidderName: openrtb_ext.NormalizeBidderName, } // execute diff --git a/endpoints/getuids.go b/endpoints/getuids.go index ad984a8df00..ea87ce70568 100644 --- a/endpoints/getuids.go +++ b/endpoints/getuids.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/usersync" "encoding/json" ) @@ -18,9 +18,11 @@ type userSyncs struct { // returns all the existing syncs for the user func NewGetUIDsEndpoint(cfg config.HostCookie) httprouter.Handle { return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - pc := usersync.ParseCookieFromRequest(r, &cfg) + cookie := usersync.ReadCookie(r, usersync.Base64Decoder{}, &cfg) + usersync.SyncHostCookie(r, cookie, &cfg) + userSyncs := new(userSyncs) - userSyncs.BuyerUIDs = pc.GetUIDs() + userSyncs.BuyerUIDs = cookie.GetUIDs() json.NewEncoder(w).Encode(userSyncs) }) } diff --git a/endpoints/getuids_test.go b/endpoints/getuids_test.go index 7988acbaffe..c496d3e270b 100644 --- a/endpoints/getuids_test.go +++ b/endpoints/getuids_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index 989f70b848f..7cbad5e26f6 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -1,17 +1,18 @@ package info import ( - "encoding/json" "net/http" "sort" "strings" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) -var invalidEnabledOnly = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) +var invalidEnabledOnlyMsg = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) +var invalidBaseAdaptersOnlyMsg = []byte(`Invalid value for 'baseadaptersonly' query param, must be of boolean type`) // NewBiddersEndpoint builds a handler for the /info/bidders endpoint. func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { @@ -20,43 +21,74 @@ func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) h glog.Fatalf("error creating /info/bidders endpoint all bidders response: %v", err) } + responseAllBaseOnly, err := prepareBiddersResponseAllBaseOnly(bidders) + if err != nil { + glog.Fatalf("error creating /info/bidders endpoint all bidders (base adapters only) response: %v", err) + } + responseEnabledOnly, err := prepareBiddersResponseEnabledOnly(bidders, aliases) if err != nil { glog.Fatalf("error creating /info/bidders endpoint enabled only response: %v", err) } + responseEnabledOnlyBaseOnly, err := prepareBiddersResponseEnabledOnlyBaseOnly(bidders) + if err != nil { + glog.Fatalf("error creating /info/bidders endpoint enabled only (base adapters only) response: %v", err) + } + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - var writeErr error - switch readEnabledOnly(r) { - case "true": - w.Header().Set("Content-Type", "application/json") - _, writeErr = w.Write(responseEnabledOnly) - case "false": - w.Header().Set("Content-Type", "application/json") - _, writeErr = w.Write(responseAll) - default: - w.WriteHeader(http.StatusBadRequest) - _, writeErr = w.Write(invalidEnabledOnly) + enabledOnly, baseAdaptersOnly, errMsg := readQueryFlags(r) + if errMsg != nil { + writeBadRequest(w, errMsg) + return } - if writeErr != nil { - glog.Errorf("error writing response to /info/bidders: %v", writeErr) + var response []byte + switch { + case !enabledOnly && !baseAdaptersOnly: + response = responseAll + case !enabledOnly && baseAdaptersOnly: + response = responseAllBaseOnly + case enabledOnly && !baseAdaptersOnly: + response = responseEnabledOnly + case enabledOnly && baseAdaptersOnly: + response = responseEnabledOnlyBaseOnly } + writeResponse(w, response) + } +} + +func readQueryFlags(r *http.Request) (enabledOnly, baseAdaptersOnly bool, errMsg []byte) { + enabledOnly, ok := readQueryFlag(r, "enabledonly") + if !ok { + return false, false, invalidEnabledOnlyMsg } + + baseAdapterOnly, ok := readQueryFlag(r, "baseadaptersonly") + if !ok { + return false, false, invalidBaseAdaptersOnlyMsg + } + + return enabledOnly, baseAdapterOnly, nil } -func readEnabledOnly(r *http.Request) string { +func readQueryFlag(r *http.Request, queryParam string) (flag, ok bool) { q := r.URL.Query() - v, exists := q["enabledonly"] + v, exists := q[queryParam] if !exists || len(v) == 0 { - // if the enabledOnly query parameter is not specified, default to false to match - // previous behavior of returning all adapters regardless of their enabled status. - return "false" + return false, true } - return strings.ToLower(v[0]) + switch strings.ToLower(v[0]) { + case "true": + return true, true + case "false": + return false, true + default: + return false, false + } } func prepareBiddersResponseAll(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { @@ -71,8 +103,20 @@ func prepareBiddersResponseAll(bidders config.BidderInfos, aliases map[string]st } sort.Strings(bidderNames) + return jsonutil.Marshal(bidderNames) +} + +func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, error) { + bidderNames := make([]string, 0, len(bidders)) + + for name, info := range bidders { + if len(info.AliasOf) == 0 { + bidderNames = append(bidderNames, name) + } + } - return json.Marshal(bidderNames) + sort.Strings(bidderNames) + return jsonutil.Marshal(bidderNames) } func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { @@ -91,6 +135,34 @@ func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[s } sort.Strings(bidderNames) + return jsonutil.Marshal(bidderNames) +} + +func prepareBiddersResponseEnabledOnlyBaseOnly(bidders config.BidderInfos) ([]byte, error) { + bidderNames := make([]string, 0, len(bidders)) + + for name, info := range bidders { + if info.IsEnabled() && len(info.AliasOf) == 0 { + bidderNames = append(bidderNames, name) + } + } + + sort.Strings(bidderNames) + return jsonutil.Marshal(bidderNames) +} - return json.Marshal(bidderNames) +func writeBadRequest(w http.ResponseWriter, data []byte) { + w.WriteHeader(http.StatusBadRequest) + writeWithErrorHandling(w, data) +} + +func writeResponse(w http.ResponseWriter, data []byte) { + w.Header().Set("Content-Type", "application/json") + writeWithErrorHandling(w, data) +} + +func writeWithErrorHandling(w http.ResponseWriter, data []byte) { + if _, err := w.Write(data); err != nil { + glog.Errorf("error writing response to /info/bidders: %v", err) + } } diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 1446e3ac22a..fbc9ab43486 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -8,8 +8,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( @@ -27,7 +28,11 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]str return func(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { bidder := ps.ByName("bidderName") - if response, ok := responses[bidder]; ok { + coreBidderName, found := getNormalisedBidderName(bidder, aliases) + if !found { + w.WriteHeader(http.StatusNotFound) + } + if response, ok := responses[coreBidderName]; ok { w.Header().Set("Content-Type", "application/json") if _, err := w.Write(response); err != nil { glog.Errorf("error writing response to /info/bidders/%s: %v", bidder, err) @@ -38,6 +43,20 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]str } } +func getNormalisedBidderName(bidderName string, aliases map[string]string) (string, bool) { + if strings.ToLower(bidderName) == "all" { + return "all", true + } + coreBidderName, ok := openrtb_ext.NormalizeBidderName(bidderName) + if !ok { //check default aliases if not found in coreBidders + if _, isDefaultAlias := aliases[bidderName]; isDefaultAlias { + return bidderName, true + } + return "", false + } + return coreBidderName.String(), true +} + func prepareBiddersDetailResponse(bidders config.BidderInfos, aliases map[string]string) (map[string][]byte, error) { details, err := mapDetails(bidders, aliases) if err != nil { @@ -83,7 +102,7 @@ func marshalDetailsResponse(details map[string]bidderDetail) (map[string][]byte, responses := map[string][]byte{} for bidder, detail := range details { - json, err := json.Marshal(detail) + json, err := jsonutil.Marshal(detail) if err != nil { return nil, fmt.Errorf("unable to marshal info for bidder %s: %v", bidder, err) } @@ -100,7 +119,7 @@ func marshalAllResponse(responses map[string][]byte) ([]byte, error) { responsesJSON[k] = json.RawMessage(v) } - json, err := json.Marshal(responsesJSON) + json, err := jsonutil.Marshal(responsesJSON) if err != nil { return nil, fmt.Errorf("unable to marshal info for bidder all: %v", err) } @@ -122,6 +141,7 @@ type maintainer struct { type capabilities struct { App *platform `json:"app,omitempty"` Site *platform `json:"site,omitempty"` + DOOH *platform `json:"dooh,omitempty"` } type platform struct { @@ -157,6 +177,12 @@ func mapDetailFromConfig(c config.BidderInfo) bidderDetail { MediaTypes: mapMediaTypes(c.Capabilities.Site.MediaTypes), } } + + if c.Capabilities.DOOH != nil { + bidderDetail.Capabilities.DOOH = &platform{ + MediaTypes: mapMediaTypes(c.Capabilities.DOOH.MediaTypes), + } + } } } else { bidderDetail.Status = statusDisabled diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index 47e5a0688a8..2911aa8e0e9 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -2,14 +2,15 @@ package info import ( "bytes" + "fmt" "io" "net/http" "net/http/httptest" "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -225,6 +226,7 @@ func TestMapDetailFromConfig(t *testing.T) { Capabilities: &config.CapabilitiesInfo{ App: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}, Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, + DOOH: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeNative}}, }, }, expected: bidderDetail{ @@ -236,6 +238,7 @@ func TestMapDetailFromConfig(t *testing.T) { Capabilities: &capabilities{ App: &platform{MediaTypes: []string{"banner"}}, Site: &platform{MediaTypes: []string{"video"}}, + DOOH: &platform{MediaTypes: []string{"native"}}, }, AliasOf: "", }, @@ -363,22 +366,22 @@ func TestMapMediaTypes(t *testing.T) { func TestBiddersDetailHandler(t *testing.T) { bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) - aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"a"}`) + aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"appnexus"}`) bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) allResponse := bytes.Buffer{} - allResponse.WriteString(`{"a":`) - allResponse.Write(bidderAResponse) - allResponse.WriteString(`,"aAlias":`) + allResponse.WriteString(`{"aAlias":`) allResponse.Write(aliasAResponse) - allResponse.WriteString(`,"b":`) + allResponse.WriteString(`,"appnexus":`) + allResponse.Write(bidderAResponse) + allResponse.WriteString(`,"rubicon":`) allResponse.Write(bidderBResponse) allResponse.WriteString(`}`) - bidders := config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo} - aliases := map[string]string{"aAlias": "a"} + bidders := config.BidderInfos{"appnexus": bidderAInfo, "rubicon": bidderBInfo} + aliases := map[string]string{"aAlias": "appnexus"} handler := NewBiddersDetailEndpoint(bidders, aliases) @@ -391,14 +394,21 @@ func TestBiddersDetailHandler(t *testing.T) { }{ { description: "Bidder A", - givenBidder: "a", + givenBidder: "appnexus", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderAResponse, }, { description: "Bidder B", - givenBidder: "b", + givenBidder: "rubicon", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: bidderBResponse, + }, + { + description: "Bidder B - case insensitive", + givenBidder: "RUBICON", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderBResponse, @@ -410,6 +420,13 @@ func TestBiddersDetailHandler(t *testing.T) { expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: aliasAResponse, }, + { + description: "Bidder A Alias - case insensitive", + givenBidder: "aAlias", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: aliasAResponse, + }, { description: "All Bidders", givenBidder: "all", @@ -418,11 +435,11 @@ func TestBiddersDetailHandler(t *testing.T) { expectedResponse: allResponse.Bytes(), }, { - description: "All Bidders - Wrong Case", - givenBidder: "ALL", - expectedStatus: http.StatusNotFound, - expectedHeaders: http.Header{}, - expectedResponse: []byte{}, + description: "All Bidders - Case insensitive", + givenBidder: "All", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: allResponse.Bytes(), }, { description: "Invalid Bidder", @@ -434,16 +451,19 @@ func TestBiddersDetailHandler(t *testing.T) { } for _, test := range testCases { - responseRecorder := httptest.NewRecorder() - handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) + t.Run(test.description, func(t *testing.T) { + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) - result := responseRecorder.Result() - assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") - resultBody, _ := io.ReadAll(result.Body) - assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") + resultBody, _ := io.ReadAll(result.Body) + fmt.Println(string(test.expectedResponse)) + assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") - resultHeaders := result.Header - assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + resultHeaders := result.Header + assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + }) } } diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index 8aec0972f74..189eb865551 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -6,244 +6,476 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) func TestPrepareBiddersResponseAll(t *testing.T) { var ( - enabled = config.BidderInfo{Disabled: false} - disabled = config.BidderInfo{Disabled: true} + enabledCore = config.BidderInfo{Disabled: false} + enabledAlias = config.BidderInfo{Disabled: false, AliasOf: "something"} + disabledCore = config.BidderInfo{Disabled: true} + disabledAlias = config.BidderInfo{Disabled: true, AliasOf: "something"} ) testCases := []struct { - description string + name string + givenBidders config.BidderInfos + givenRequestAliases map[string]string + expected string + }{ + { + name: "none", + givenBidders: config.BidderInfos{}, + givenRequestAliases: nil, + expected: `[]`, + }, + { + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, + givenRequestAliases: nil, + expected: `["a"]`, + }, + { + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, + givenRequestAliases: nil, + expected: `["a"]`, + }, + { + name: "core-one-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, + givenRequestAliases: nil, + expected: `["a","b"]`, + }, + { + name: "core-one-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + givenRequestAliases: nil, + expected: `["a","z"]`, + }, + { + name: "alias-one", + givenBidders: config.BidderInfos{"a": enabledAlias}, + givenRequestAliases: nil, + expected: `["a"]`, + }, + { + name: "alias-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + givenRequestAliases: nil, + expected: `["a","b","c","d"]`, + }, + { + name: "alias-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, + givenRequestAliases: nil, + expected: `["a","z"]`, + }, + { + name: "defaultrequest-one", + givenBidders: config.BidderInfos{"a": enabledCore}, + givenRequestAliases: map[string]string{"b": "a"}, + expected: `["a","b"]`, + }, + { + name: "defaultrequest-mixed", + givenBidders: config.BidderInfos{"a": enabledCore, "b": disabledCore}, + givenRequestAliases: map[string]string{"x": "a", "y": "b"}, + expected: `["a","b","x","y"]`, + }, + { + name: "defaultrequest-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore}, + givenRequestAliases: map[string]string{"a": "z"}, + expected: `["a","z"]`, + }, + { + name: "mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + givenRequestAliases: map[string]string{"z": "a"}, + expected: `["a","b","c","d","z"]`, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + result, err := prepareBiddersResponseAll(test.givenBidders, test.givenRequestAliases) + assert.NoError(t, err) + assert.Equal(t, []byte(test.expected), result) + }) + } +} + +func TestPrepareBiddersResponseAllBaseOnly(t *testing.T) { + var ( + enabledCore = config.BidderInfo{Disabled: false} + enabledAlias = config.BidderInfo{Disabled: false, AliasOf: "something"} + disabledCore = config.BidderInfo{Disabled: true} + disabledAlias = config.BidderInfo{Disabled: true, AliasOf: "something"} + ) + + testCases := []struct { + name string givenBidders config.BidderInfos - givenAliases map[string]string expected string }{ { - description: "None", + name: "none", givenBidders: config.BidderInfos{}, - givenAliases: nil, expected: `[]`, }, { - description: "Core Bidders Only - One - Enabled", - givenBidders: config.BidderInfos{"a": enabled}, - givenAliases: nil, + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, expected: `["a"]`, }, { - description: "Core Bidders Only - One - Disabled", - givenBidders: config.BidderInfos{"a": disabled}, - givenAliases: nil, + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, expected: `["a"]`, }, { - description: "Core Bidders Only - Many", - givenBidders: config.BidderInfos{"a": enabled, "b": enabled}, - givenAliases: nil, + name: "core-one-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, expected: `["a","b"]`, }, { - description: "Core Bidders Only - Many - Mixed", - givenBidders: config.BidderInfos{"a": disabled, "b": enabled}, - givenAliases: nil, - expected: `["a","b"]`, + name: "core-one-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + expected: `["a","z"]`, }, { - description: "Core Bidders Only - Many - Sorted", - givenBidders: config.BidderInfos{"b": enabled, "a": enabled}, - givenAliases: nil, - expected: `["a","b"]`, + name: "alias-one", + givenBidders: config.BidderInfos{"a": enabledAlias}, + expected: `[]`, }, { - description: "With Aliases - One", - givenBidders: config.BidderInfos{"a": enabled}, - givenAliases: map[string]string{"b": "a"}, - expected: `["a","b"]`, + name: "alias-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + expected: `["a","c"]`, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + result, err := prepareBiddersResponseAllBaseOnly(test.givenBidders) + assert.NoError(t, err) + assert.Equal(t, []byte(test.expected), result) + }) + } +} + +func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { + var ( + enabledCore = config.BidderInfo{Disabled: false} + enabledAlias = config.BidderInfo{Disabled: false, AliasOf: "something"} + disabledCore = config.BidderInfo{Disabled: true} + disabledAlias = config.BidderInfo{Disabled: true, AliasOf: "something"} + ) + + testCases := []struct { + name string + givenBidders config.BidderInfos + givenRequestAliases map[string]string + expected string + }{ + { + name: "none", + givenBidders: config.BidderInfos{}, + givenRequestAliases: nil, + expected: `[]`, }, { - description: "With Aliases - Many", - givenBidders: config.BidderInfos{"a": enabled, "b": disabled}, - givenAliases: map[string]string{"x": "a", "y": "b"}, - expected: `["a","b","x","y"]`, + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, + givenRequestAliases: nil, + expected: `["a"]`, }, { - description: "With Aliases - Sorted", - givenBidders: config.BidderInfos{"z": enabled}, - givenAliases: map[string]string{"a": "z"}, - expected: `["a","z"]`, + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, + givenRequestAliases: nil, + expected: `[]`, + }, + { + name: "core-one-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, + givenRequestAliases: nil, + expected: `["b"]`, + }, + { + name: "core-one-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + givenRequestAliases: nil, + expected: `["a","z"]`, + }, + { + name: "alias-one", + givenBidders: config.BidderInfos{"a": enabledAlias}, + givenRequestAliases: nil, + expected: `["a"]`, + }, + { + name: "alias-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + givenRequestAliases: nil, + expected: `["c","d"]`, + }, + { + name: "alias-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, + givenRequestAliases: nil, + expected: `["a","z"]`, + }, + { + name: "defaultrequest-one", + givenBidders: config.BidderInfos{"a": enabledCore}, + givenRequestAliases: map[string]string{"b": "a"}, + expected: `["a","b"]`, + }, + { + name: "defaultrequest-mixed", + givenBidders: config.BidderInfos{"a": enabledCore, "b": disabledCore}, + givenRequestAliases: map[string]string{"x": "a", "y": "b"}, + expected: `["a","x"]`, + }, + { + name: "defaultrequest-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore}, + givenRequestAliases: map[string]string{"a": "z"}, + expected: `["a","z"]`, + }, + { + name: "mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + givenRequestAliases: map[string]string{"z": "a"}, + expected: `["c","d"]`, }, } for _, test := range testCases { - result, err := prepareBiddersResponseAll(test.givenBidders, test.givenAliases) - - assert.NoError(t, err, test.description) - assert.Equal(t, []byte(test.expected), result, test.description) + t.Run(test.name, func(t *testing.T) { + result, err := prepareBiddersResponseEnabledOnly(test.givenBidders, test.givenRequestAliases) + assert.NoError(t, err) + assert.Equal(t, []byte(test.expected), result) + }) } } -func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { +func TestPrepareBiddersResponseEnabledOnlyBaseOnly(t *testing.T) { var ( - enabled = config.BidderInfo{Disabled: false} - disabled = config.BidderInfo{Disabled: true} + enabledCore = config.BidderInfo{Disabled: false} + enabledAlias = config.BidderInfo{Disabled: false, AliasOf: "something"} + disabledCore = config.BidderInfo{Disabled: true} + disabledAlias = config.BidderInfo{Disabled: true, AliasOf: "something"} ) testCases := []struct { - description string + name string givenBidders config.BidderInfos - givenAliases map[string]string expected string }{ { - description: "None", + name: "none", givenBidders: config.BidderInfos{}, - givenAliases: nil, expected: `[]`, }, { - description: "Core Bidders Only - One - Enabled", - givenBidders: config.BidderInfos{"a": enabled}, - givenAliases: nil, + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, expected: `["a"]`, }, { - description: "Core Bidders Only - One - Disabled", - givenBidders: config.BidderInfos{"a": disabled}, - givenAliases: nil, + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, expected: `[]`, }, { - description: "Core Bidders Only - Many", - givenBidders: config.BidderInfos{"a": enabled, "b": enabled}, - givenAliases: nil, - expected: `["a","b"]`, - }, - { - description: "Core Bidders Only - Many - Mixed", - givenBidders: config.BidderInfos{"a": disabled, "b": enabled}, - givenAliases: nil, + name: "core-one-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, expected: `["b"]`, }, { - description: "Core Bidders Only - Many - Sorted", - givenBidders: config.BidderInfos{"b": enabled, "a": enabled}, - givenAliases: nil, - expected: `["a","b"]`, + name: "core-one-mixed-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + expected: `["a","z"]`, }, { - description: "With Aliases - One", - givenBidders: config.BidderInfos{"a": enabled}, - givenAliases: map[string]string{"b": "a"}, - expected: `["a","b"]`, + name: "alias-one", + givenBidders: config.BidderInfos{"a": enabledAlias}, + expected: `[]`, }, { - description: "With Aliases - Many", - givenBidders: config.BidderInfos{"a": enabled, "b": disabled}, - givenAliases: map[string]string{"x": "a", "y": "b"}, - expected: `["a","x"]`, + name: "alias-many", + givenBidders: config.BidderInfos{"a": enabledAlias, "b": enabledAlias}, + expected: `[]`, }, { - description: "With Aliases - Sorted", - givenBidders: config.BidderInfos{"z": enabled}, - givenAliases: map[string]string{"a": "z"}, - expected: `["a","z"]`, + name: "mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + expected: `["c"]`, }, } for _, test := range testCases { - result, err := prepareBiddersResponseEnabledOnly(test.givenBidders, test.givenAliases) - - assert.NoError(t, err, test.description) - assert.Equal(t, []byte(test.expected), result, test.description) + t.Run(test.name, func(t *testing.T) { + result, err := prepareBiddersResponseEnabledOnlyBaseOnly(test.givenBidders) + assert.NoError(t, err) + assert.Equal(t, []byte(test.expected), result) + }) } } func TestBiddersHandler(t *testing.T) { var ( - enabled = config.BidderInfo{Disabled: false} - disabled = config.BidderInfo{Disabled: true} + enabledCore = config.BidderInfo{Disabled: false} + enabledAlias = config.BidderInfo{Disabled: false, AliasOf: "something"} + disabledCore = config.BidderInfo{Disabled: true} + disabledAlias = config.BidderInfo{Disabled: true, AliasOf: "something"} ) - bidders := config.BidderInfos{"a": enabled, "b": disabled} - aliases := map[string]string{"x": "a", "y": "b"} + bidders := config.BidderInfos{"a": enabledCore, "b": enabledAlias, "c": disabledCore, "d": disabledAlias} + aliases := map[string]string{"x": "a", "y": "c"} testCases := []struct { - description string + name string givenURL string expectedStatus int expectedBody string expectedHeaders http.Header }{ { - description: "No Query Parameters - Backwards Compatibility", + name: "simple", givenURL: "/info/bidders", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x","y"]`, + expectedBody: `["a","b","c","d","x","y"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { - description: "Enabled Only - False", + name: "enabledonly-false", givenURL: "/info/bidders?enabledonly=false", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x","y"]`, + expectedBody: `["a","b","c","d","x","y"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { - description: "Enabled Only - False - Case Insensitive", + name: "enabledonly-false-caseinsensitive", givenURL: "/info/bidders?enabledonly=fAlSe", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x","y"]`, + expectedBody: `["a","b","c","d","x","y"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { - description: "Enabled Only - True", + name: "enabledonly-true", givenURL: "/info/bidders?enabledonly=true", expectedStatus: http.StatusOK, - expectedBody: `["a","x"]`, + expectedBody: `["a","b","x"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { - description: "Enabled Only - True - Case Insensitive", + name: "enabledonly-true-caseinsensitive", givenURL: "/info/bidders?enabledonly=TrUe", expectedStatus: http.StatusOK, - expectedBody: `["a","x"]`, + expectedBody: `["a","b","x"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { - description: "Enabled Only - Invalid", + name: "enabledonly-invalid", givenURL: "/info/bidders?enabledonly=foo", expectedStatus: http.StatusBadRequest, expectedBody: `Invalid value for 'enabledonly' query param, must be of boolean type`, expectedHeaders: http.Header{}, }, { - description: "Enabled Only - Missing Value", + name: "enabledonly-missing", givenURL: "/info/bidders?enabledonly=", expectedStatus: http.StatusBadRequest, expectedBody: `Invalid value for 'enabledonly' query param, must be of boolean type`, expectedHeaders: http.Header{}, }, + { + name: "baseonly-false", + givenURL: "/info/bidders?baseadaptersonly=false", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","c","d","x","y"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "baseonly-false-caseinsensitive", + givenURL: "/info/bidders?baseadaptersonly=fAlSe", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","c","d","x","y"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "baseonly-true", + givenURL: "/info/bidders?baseadaptersonly=true", + expectedStatus: http.StatusOK, + expectedBody: `["a","c"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "baseonly-true-caseinsensitive", + givenURL: "/info/bidders?baseadaptersonly=TrUe", + expectedStatus: http.StatusOK, + expectedBody: `["a","c"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "baseonly-invalid", + givenURL: "/info/bidders?baseadaptersonly=foo", + expectedStatus: http.StatusBadRequest, + expectedBody: `Invalid value for 'baseadaptersonly' query param, must be of boolean type`, + expectedHeaders: http.Header{}, + }, + { + name: "baseonly-missing", + givenURL: "/info/bidders?baseadaptersonly=", + expectedStatus: http.StatusBadRequest, + expectedBody: `Invalid value for 'baseadaptersonly' query param, must be of boolean type`, + expectedHeaders: http.Header{}, + }, + { + name: "enabledonly-true-baseonly-false", + givenURL: "/info/bidders?enabledonly=true&baseadaptersonly=false", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","x"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "enabledonly-false-baseonly-true", + givenURL: "/info/bidders?enabledonly=false&baseadaptersonly=true", + expectedStatus: http.StatusOK, + expectedBody: `["a","c"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + name: "enabledonly-true-baseonly-true", + givenURL: "/info/bidders?enabledonly=true&baseadaptersonly=true", + expectedStatus: http.StatusOK, + expectedBody: `["a"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, } for _, test := range testCases { - handler := NewBiddersEndpoint(bidders, aliases) + t.Run(test.name, func(t *testing.T) { + handler := NewBiddersEndpoint(bidders, aliases) - request := httptest.NewRequest("GET", test.givenURL, nil) + request := httptest.NewRequest("GET", test.givenURL, nil) - responseRecorder := httptest.NewRecorder() - handler(responseRecorder, request, nil) + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, request, nil) - result := responseRecorder.Result() - assert.Equal(t, result.StatusCode, test.expectedStatus) + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, test.expectedStatus) - resultBody, _ := io.ReadAll(result.Body) - assert.Equal(t, []byte(test.expectedBody), resultBody) + resultBody, _ := io.ReadAll(result.Body) + assert.Equal(t, []byte(test.expectedBody), resultBody) - resultHeaders := result.Header - assert.Equal(t, test.expectedHeaders, resultHeaders) + resultHeaders := result.Header + assert.Equal(t, test.expectedHeaders, resultHeaders) + }) } } diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 73f4842ae83..db427f380ed 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -16,27 +16,29 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/amp" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/version" ) const defaultAmpRequestTimeoutMillis = 900 @@ -62,19 +64,20 @@ func NewAmpEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, storedRespFetcher stored_requests.Fetcher, hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, + tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewAmpEndpoint requires non-nil arguments.") } - defRequest := defReqJSON != nil && len(defReqJSON) > 0 + defRequest := len(defReqJSON) > 0 ipValidator := iputil.PublicNetworkIPValidator{ IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, @@ -90,7 +93,7 @@ func NewAmpEndpoint( accounts, cfg, metricsEngine, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -99,7 +102,10 @@ func NewAmpEndpoint( nil, ipValidator, storedRespFetcher, - hookExecutionPlanBuilder}).AmpAuction), nil + hookExecutionPlanBuilder, + tmaxAdjustments, + openrtb_ext.NormalizeBidderName, + }).AmpAuction), nil } @@ -129,11 +135,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h CookieFlag: metrics.CookieFlagUnknown, RequestStatus: metrics.RequestStatusOK, } + activityControl := privacy.ActivityControl{} defer func() { deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogAmpObject(&ao) + deps.analytics.LogAmpObject(&ao, activityControl) }() // Add AMP headers @@ -181,12 +188,15 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } defer cancel() - usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) + // Read UserSyncs/Cookie from Request + usersyncs := usersync.ReadCookie(r, usersync.Base64Decoder{}, &deps.cfg.HostCookie) + usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie) if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes } else { labels.CookieFlag = metrics.CookieFlagNo } + labels.PubID = getAccountID(reqWrapper.Site.Publisher) // Look up account now that we have resolved the pubID value account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID, deps.metricsEngine) @@ -200,7 +210,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h metricsStatus := metrics.RequestStatusBadInput for _, er := range errL { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode { + if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable metricsStatus = metrics.RequestStatusBlacklisted break @@ -222,6 +232,10 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + activityControl = privacy.NewActivityControl(&account.Privacy) + + hookExecutor.SetActivityControl(activityControl) + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ @@ -239,9 +253,16 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h HookExecutor: hookExecutor, QueryParams: r.URL.Query(), TCF2Config: tcf2Config, + Activities: activityControl, + TmaxAdjustments: deps.tmaxAdjustments, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) + defer func() { + if !auctionRequest.BidderResponseStartTime.IsZero() { + deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime)) + } + }() var response *openrtb2.BidResponse if auctionResponse != nil { response = auctionResponse.BidResponse @@ -275,9 +296,6 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } labels, ao = sendAmpResponse(w, hookExecutor, auctionResponse, reqWrapper, account, labels, ao, errL) - if len(ao.Errors) == 0 { - recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine) - } } func rejectAmpRequest( @@ -326,7 +344,7 @@ func sendAmpResponse( // but this is a very unlikely corner case. Doing this so we can catch "hb_cache_id" // and "hb_cache_id_{deal}", which allows for deal support in AMP. bidExt := &openrtb_ext.ExtBid{} - err := json.Unmarshal(bid.Ext, bidExt) + err := jsonutil.Unmarshal(bid.Ext, bidExt) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Critical error while unpacking AMP targets: %v", err) @@ -345,7 +363,7 @@ func sendAmpResponse( // Extract global targeting var extResponse openrtb_ext.ExtBidResponse - eRErr := json.Unmarshal(response.Ext, &extResponse) + eRErr := jsonutil.Unmarshal(response.Ext, &extResponse) if eRErr != nil { ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } @@ -394,7 +412,7 @@ func getExtBidResponse( } // Extract any errors var extResponse openrtb_ext.ExtBidResponse - eRErr := json.Unmarshal(response.Ext, &extResponse) + eRErr := jsonutil.Unmarshal(response.Ext, &extResponse) if eRErr != nil { ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } @@ -509,12 +527,12 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req // The fetched config becomes the entire OpenRTB request requestJSON := storedRequests[ampParams.StoredRequestID] - if err := json.Unmarshal(requestJSON, req); err != nil { + if err := jsonutil.UnmarshalValid(requestJSON, req); err != nil { errs = []error{err} return } - storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, &openrtb_ext.RequestWrapper{BidRequest: req}, deps.storedRespFetcher) if err != nil { errs = []error{err} return @@ -809,7 +827,7 @@ func setTrace(req *openrtb2.BidRequest, value string) error { return nil } - ext, err := json.Marshal(map[string]map[string]string{"prebid": {"trace": value}}) + ext, err := jsonutil.Marshal(map[string]map[string]string{"prebid": {"trace": value}}) if err != nil { return err } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 27f84aee42c..bd56457b3d7 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -18,19 +18,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/amp" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly. @@ -49,6 +51,9 @@ func TestGoodAmpRequests(t *testing.T) { "imp-with-stored-resp.json", "gdpr-no-consentstring.json", "gdpr.json", + "buyeruids-case-insensitive.json", + "buyeruids-camel-case.json", + "aliased-buyeruids-case-insensitive.json", }, }, { @@ -73,7 +78,7 @@ func TestGoodAmpRequests(t *testing.T) { } test := testCase{} - if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) { + if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) { continue } @@ -88,7 +93,7 @@ func TestGoodAmpRequests(t *testing.T) { continue } - test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest} + test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest} test.endpointType = AMP_ENDPOINT cfg := &config.Configuration{ @@ -98,8 +103,6 @@ func TestGoodAmpRequests(t *testing.T) { if test.Config != nil { cfg.BlacklistedApps = test.Config.BlacklistedApps cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() - cfg.BlacklistedAccts = test.Config.BlacklistedAccounts - cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() cfg.AccountRequired = test.Config.AccountRequired } @@ -121,14 +124,14 @@ func TestGoodAmpRequests(t *testing.T) { // Assertions if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, filename) { if test.ExpectedReturnCode == http.StatusOK { - assert.JSONEq(t, string(test.ExpectedAmpResponse), string(recorder.Body.Bytes()), "Not the expected response. Test file: %s", filename) + assert.JSONEq(t, string(test.ExpectedAmpResponse), recorder.Body.String(), "Not the expected response. Test file: %s", filename) } else { assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), filename) } } if test.ExpectedValidatedBidReq != nil { // compare as json to ignore whitespace and ext field ordering - actualJson, err := json.Marshal(ex.actualValidatedBidReq) + actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq) if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) { assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) } @@ -148,11 +151,6 @@ func TestAccountErrors(t *testing.T) { storedReqID: "1", filename: "account-malformed/malformed-acct.json", }, - { - description: "Blocked account", - storedReqID: "1", - filename: "blacklisted/blacklisted-site-publisher.json", - }, } for _, tt := range tests { @@ -162,16 +160,14 @@ func TestAccountErrors(t *testing.T) { } test := testCase{} - if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) { + if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) { continue } - test.storedRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest} + test.StoredRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest} test.endpointType = AMP_ENDPOINT cfg := &config.Configuration{ - BlacklistedAccts: []string{"bad_acct"}, - BlacklistedAcctMap: map[string]bool{"bad_acct": true}, - MaxRequestSize: maxSize, + MaxRequestSize: maxSize, } cfg.MarshalAccountDefaults() @@ -210,12 +206,13 @@ func TestAMPPageInfo(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&curl=%s", url.QueryEscape(page)), nil) recorder := httptest.NewRecorder() @@ -313,12 +310,13 @@ func TestGDPRConsent(t *testing.T) { GDPR: config.GDPR{Enabled: true}, }, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) // Invoke Endpoint @@ -328,7 +326,7 @@ func TestGDPRConsent(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -344,7 +342,7 @@ func TestGDPRConsent(t *testing.T) { return } var ue openrtb_ext.ExtUser - err = json.Unmarshal(result.User.Ext, &ue) + err = jsonutil.UnmarshalValid(result.User.Ext, &ue) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -359,7 +357,7 @@ func TestGDPRConsent(t *testing.T) { // Parse Resonse var responseLegacy AmpResponse - if err := json.Unmarshal(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -375,7 +373,7 @@ func TestGDPRConsent(t *testing.T) { return } var ueLegacy openrtb_ext.ExtUser - err = json.Unmarshal(resultLegacy.User.Ext, &ueLegacy) + err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy) if !assert.NoError(t, err, test.description+":legacy:deserialize") { return } @@ -522,7 +520,7 @@ func TestOverrideWithParams(t *testing.T) { Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, User: &openrtb2.User{Ext: json.RawMessage(`malformed`)}, }, - errorMsgs: []string{"invalid character 'm' looking for beginning of value"}, + errorMsgs: []string{"expect { or n, but found m"}, expectFatalErrors: true, }, }, @@ -570,7 +568,7 @@ func TestOverrideWithParams(t *testing.T) { User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, }, - errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"}, + errorMsgs: []string{"expect \" after {, but found m"}, expectFatalErrors: true, }, }, @@ -736,12 +734,13 @@ func TestCCPAConsent(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) // Invoke Endpoint @@ -751,7 +750,7 @@ func TestCCPAConsent(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -767,7 +766,7 @@ func TestCCPAConsent(t *testing.T) { return } var re openrtb_ext.ExtRegs - err = json.Unmarshal(result.Regs.Ext, &re) + err = jsonutil.UnmarshalValid(result.Regs.Ext, &re) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -849,12 +848,13 @@ func TestConsentWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) // Invoke Endpoint @@ -872,7 +872,7 @@ func TestConsentWarnings(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -947,12 +947,13 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { GDPR: config.GDPR{Enabled: true}, }, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) // Invoke Endpoint @@ -962,7 +963,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { // Parse Response var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -978,7 +979,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { return } var ue openrtb_ext.ExtUser - err = json.Unmarshal(result.User.Ext, &ue) + err = jsonutil.UnmarshalValid(result.User.Ext, &ue) if !assert.NoError(t, err, test.description+":deserialize") { return } @@ -1001,12 +1002,13 @@ func TestAMPSiteExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), nil, nil, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) request, err := http.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) if !assert.NoError(t, err) { @@ -1043,12 +1045,13 @@ func TestAmpBadRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) for requestID := range badRequests { request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil) @@ -1076,12 +1079,13 @@ func TestAmpDebug(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) for requestID := range requests { @@ -1096,7 +1100,7 @@ func TestAmpDebug(t *testing.T) { } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } @@ -1131,7 +1135,7 @@ func TestInitAmpTargetingAndCache(t *testing.T) { { name: "malformed", request: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, - expectedErrs: []string{"invalid character 'm' looking for beginning of value"}, + expectedErrs: []string{"expect { or n, but found m"}, }, { name: "nil", @@ -1211,12 +1215,13 @@ func TestQueryParamOverrides(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) requestID := "1" @@ -1236,12 +1241,12 @@ func TestQueryParamOverrides(t *testing.T) { } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } var resolvedRequest openrtb2.BidRequest - err := json.Unmarshal(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) + err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) assert.NoError(t, err, "resolved request should have a correct format") if resolvedRequest.TMax != timeout { t.Errorf("Expected TMax to equal timeout (%d), got: %d", timeout, resolvedRequest.TMax) @@ -1368,12 +1373,13 @@ func (s formatOverrideSpec) execute(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account) @@ -1386,11 +1392,11 @@ func (s formatOverrideSpec) execute(t *testing.T) { t.Errorf("Request was: %s", string(requests["1"])) } var response AmpResponse - if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("Error unmarshalling response: %s", err.Error()) } var resolvedRequest openrtb2.BidRequest - err := json.Unmarshal(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) + err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest) assert.NoError(t, err, "resolved request should have the correct format") formats := resolvedRequest.Imp[0].Banner.Format if len(formats) != len(s.expect) { @@ -1440,14 +1446,14 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *excha if len(auctionRequest.StoredAuctionResponses) > 0 { var seatBids []openrtb2.SeatBid - if err := json.Unmarshal(auctionRequest.StoredAuctionResponses[r.BidRequest.Imp[0].ID], &seatBids); err != nil { + if err := jsonutil.UnmarshalValid(auctionRequest.StoredAuctionResponses[r.BidRequest.Imp[0].ID], &seatBids); err != nil { return nil, err } response.SeatBid = seatBids } if r.BidRequest.Test == 1 { - resolvedRequest, err := json.Marshal(r.BidRequest) + resolvedRequest, err := jsonutil.Marshal(r.BidRequest) if err != nil { resolvedRequest = json.RawMessage("{}") } @@ -1506,7 +1512,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, var userExtData []byte if userExt != nil { var err error - userExtData, err = json.Marshal(userExt) + userExtData, err = jsonutil.Marshal(userExt) if err != nil { return nil, err } @@ -1523,7 +1529,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, var regsExtData []byte if regsExt != nil { var err error - regsExtData, err = json.Marshal(regsExt) + regsExtData, err = jsonutil.Marshal(regsExt) if err != nil { return nil, err } @@ -1535,7 +1541,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, Ext: regsExtData, } } - return json.Marshal(bidRequest) + return jsonutil.Marshal(bidRequest) } func TestSetEffectiveAmpPubID(t *testing.T) { @@ -1630,25 +1636,25 @@ type mockLogger struct { auctionObject *analytics.AuctionObject } -func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.PBSAnalyticsModule { +func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.Runner { return &mockLogger{ ampObject: ao, auctionObject: aucObj, } } -func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject) { +func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { *logger.auctionObject = *ao } -func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject) { +func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { } func (logger mockLogger) LogCookieSyncObject(cookieObject *analytics.CookieSyncObject) { } func (logger mockLogger) LogSetUIDObject(uuidObj *analytics.SetUIDObject) { } -func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent) { +func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent, _ privacy.ActivityControl) { } -func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject) { +func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { *logger.ampObject = *ao } @@ -1914,6 +1920,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) return &actualAmpObject, endpoint } @@ -1960,12 +1967,13 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) for _, test := range testCases { @@ -1995,12 +2003,13 @@ func TestRequestWithTargeting(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), nil, nil, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) url, err := url.Parse("/openrtb2/auction/amp") assert.NoError(t, err, "unexpected error received while parsing url") @@ -2183,14 +2192,14 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) { assert.NoError(t, err, "Failed to read test file.") test := testCase{} - assert.NoError(t, json.Unmarshal(fileData, &test), "Failed to parse test file.") + assert.NoError(t, jsonutil.UnmarshalValid(fileData, &test), "Failed to parse test file.") request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) recorder := httptest.NewRecorder() query := request.URL.Query() tagID := query.Get("tag_id") - test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest} + test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest} test.planBuilder = tc.planBuilder test.endpointType = AMP_ENDPOINT @@ -2203,8 +2212,8 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) { var actualAmpResp AmpResponse var expectedAmpResp AmpResponse - assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualAmpResp), "Unable to unmarshal actual AmpResponse.") - assert.NoError(t, json.Unmarshal(test.ExpectedAmpResponse, &expectedAmpResp), "Unable to unmarshal expected AmpResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualAmpResp), "Unable to unmarshal actual AmpResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedAmpResponse, &expectedAmpResp), "Unable to unmarshal expected AmpResponse.") // validate modules data separately, because it has dynamic data if expectedAmpResp.ORTB2.Ext.Prebid == nil { @@ -2240,7 +2249,7 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { { description: "Error logged when bid.ext unmarshal fails", expectedErrors: []error{ - errors.New("Critical error while unpacking AMP targets: unexpected end of JSON input"), + errors.New("Critical error while unpacking AMP targets: expect { or n, but found \""), }, expectedStatus: http.StatusInternalServerError, writer: httptest.NewRecorder(), @@ -2315,9 +2324,9 @@ func TestSendAmpResponse_LogsErrors(t *testing.T) { account := &config.Account{DebugAllow: true} reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request} - labels, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) + _, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil) - assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") + assert.Equal(t, test.expectedErrors, ao.Errors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") }) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 216bff6eaf5..eb3f5c02ccb 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "regexp" @@ -26,35 +25,36 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/ortb" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/privacy" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/lmt" + "github.com/prebid/prebid-server/v2/schain" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/httputil" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" ) const storedRequestTimeoutMillis = 50 @@ -69,13 +69,16 @@ var ( ) var accountIdSearchPath = [...]struct { - isApp bool - key []string + isApp bool + isDOOH bool + key []string }{ - {true, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, - {true, []string{"app", "publisher", "id"}}, - {false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, - {false, []string{"site", "publisher", "id"}}, + {true, false, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {true, false, []string{"app", "publisher", "id"}}, + {false, false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {false, false, []string{"site", "publisher", "id"}}, + {false, true, []string{"dooh", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, + {false, true, []string{"dooh", "publisher", "id"}}, } func NewEndpoint( @@ -86,18 +89,19 @@ func NewEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, storedRespFetcher stored_requests.Fetcher, hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, + tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewEndpoint requires non-nil arguments.") } - defRequest := defReqJSON != nil && len(defReqJSON) > 0 + defRequest := len(defReqJSON) > 0 ipValidator := iputil.PublicNetworkIPValidator{ IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, @@ -113,7 +117,7 @@ func NewEndpoint( accounts, cfg, metricsEngine, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -122,9 +126,13 @@ func NewEndpoint( nil, ipValidator, storedRespFetcher, - hookExecutionPlanBuilder}).Auction), nil + hookExecutionPlanBuilder, + tmaxAdjustments, + openrtb_ext.NormalizeBidderName}).Auction), nil } +type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) + type endpointDeps struct { uuidGenerator uuidutil.UUIDGenerator ex exchange.Exchange @@ -134,7 +142,7 @@ type endpointDeps struct { accounts stored_requests.AccountFetcher cfg *config.Configuration metricsEngine metrics.MetricsEngine - analytics analytics.PBSAnalyticsModule + analytics analytics.Runner disabledBidders map[string]string defaultRequest bool defReqJSON []byte @@ -144,6 +152,8 @@ type endpointDeps struct { privateNetworkIPValidator iputil.IPValidator storedRespFetcher stored_requests.Fetcher hookExecutionPlanBuilder hooks.ExecutionPlanBuilder + tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed + normalizeBidderName normalizeBidderName } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -170,10 +180,12 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http CookieFlag: metrics.CookieFlagUnknown, RequestStatus: metrics.RequestStatusOK, } + + activityControl := privacy.ActivityControl{} defer func() { deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogAuctionObject(&ao) + deps.analytics.LogAuctionObject(&ao, activityControl) }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) @@ -191,6 +203,10 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + activityControl = privacy.NewActivityControl(&account.Privacy) + + hookExecutor.SetActivityControl(activityControl) + ctx := context.Background() timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) @@ -200,7 +216,11 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http defer cancel() } - usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) + // Read Usersyncs/Cookie + decoder := usersync.Base64Decoder{} + usersyncs := usersync.ReadCookie(r, decoder, &deps.cfg.HostCookie) + usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie) + if req.Site != nil { if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes @@ -236,8 +256,15 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http PubID: labels.PubID, HookExecutor: hookExecutor, TCF2Config: tcf2Config, + Activities: activityControl, + TmaxAdjustments: deps.tmaxAdjustments, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) + defer func() { + if !auctionRequest.BidderResponseStartTime.IsZero() { + deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime)) + } + }() ao.RequestWrapper = req ao.Account = account var response *openrtb2.BidResponse @@ -269,9 +296,6 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http glog.Errorf("Error setting seat non-bid: %v", err) } labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao) - if len(ao.Errors) == 0 { - recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine) - } } // setSeatNonBidRaw is transitional function for setting SeatNonBid inside bidResponse.Ext @@ -287,11 +311,11 @@ func setSeatNonBidRaw(request *openrtb_ext.RequestWrapper, auctionResponse *exch // by HoldAuction response := auctionResponse.BidResponse respExt := &openrtb_ext.ExtBidResponse{} - if err := json.Unmarshal(response.Ext, &respExt); err != nil { + if err := jsonutil.Unmarshal(response.Ext, &respExt); err != nil { return err } if setSeatNonBid(respExt, request, auctionResponse) { - if respExtJson, err := json.Marshal(respExt); err == nil { + if respExtJson, err := jsonutil.Marshal(respExt); err == nil { response.Ext = respExtJson return nil } else { @@ -400,7 +424,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric N: deps.cfg.MaxRequestSize, } - requestJson, err := ioutil.ReadAll(limitedReqReader) + requestJson, err := io.ReadAll(limitedReqReader) if err != nil { errs = []error{err} return @@ -424,7 +448,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric requestJson, rejectErr := hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson) if rejectErr != nil { errs = []error{rejectErr} - if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err = jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { glog.Errorf("Failed to unmarshal BidRequest during entrypoint rejection: %s", err) } return @@ -444,12 +468,16 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - accountId, isAppReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) + accountId, isAppReq, isDOOHReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) // fill labels here in order to pass correct metrics in case of errors if isAppReq { labels.Source = metrics.DemandApp labels.RType = metrics.ReqTypeORTB2App labels.PubID = accountId + } else if isDOOHReq { + labels.Source = metrics.DemandDOOH + labels.RType = metrics.ReqTypeORTB2DOOH + labels.PubID = accountId } else { // is Site request labels.Source = metrics.DemandWeb labels.PubID = accountId @@ -468,7 +496,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric requestJson, rejectErr = hookExecutor.ExecuteRawAuctionStage(requestJson) if rejectErr != nil { errs = []error{rejectErr} - if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err = jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { glog.Errorf("Failed to unmarshal BidRequest during raw auction stage rejection: %s", err) } return @@ -491,13 +519,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - //Stored auction responses should be processed after stored requests due to possible impression modification - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) - if len(errs) > 0 { - return nil, nil, nil, nil, nil, nil, errs - } - - if err := json.Unmarshal(requestJson, req.BidRequest); err != nil { + if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { errs = []error{err} return } @@ -522,6 +544,12 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric lmt.ModifyForIOS(req.BidRequest) + //Stored auction responses should be processed after stored requests due to possible impression modification + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher) + if len(errs) > 0 { + return nil, nil, nil, nil, nil, nil, errs + } + hasStoredResponses := len(storedAuctionResponses) > 0 errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest) if len(errL) > 0 { @@ -596,7 +624,7 @@ func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { } bidderParams := map[string]map[string]json.RawMessage{} - if err := json.Unmarshal(bidderParamsJson, &bidderParams); err != nil { + if err := jsonutil.Unmarshal(bidderParamsJson, &bidderParams); err != nil { return nil } @@ -639,7 +667,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string impExtBidderMap := map[string]json.RawMessage{} if len(impExtBidder) > 0 { - if err := json.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { + if err := jsonutil.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { continue } } @@ -653,7 +681,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string } if modified { - impExtBidderJson, err := json.Marshal(impExtBidderMap) + impExtBidderJson, err := jsonutil.Marshal(impExtBidderMap) if err != nil { return fmt.Errorf("error marshalling ext.BIDDER: %s", err.Error()) } @@ -687,7 +715,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[ impExtPrebidBidderMap := map[string]json.RawMessage{} if len(impExtPrebidBidder) > 0 { - if err := json.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { + if err := jsonutil.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { continue } } @@ -701,7 +729,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[ } if modified { - impExtPrebidBidderJson, err := json.Marshal(impExtPrebidBidderMap) + impExtPrebidBidderJson, err := jsonutil.Marshal(impExtPrebidBidderMap) if err != nil { return fmt.Errorf("error marshalling ext.prebid.bidder.BIDDER: %s", err.Error()) } @@ -744,7 +772,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - var aliases map[string]string + var requestAliases map[string]string reqExt, err := req.GetRequestExt() if err != nil { return []error{fmt.Errorf("request.ext is invalid: %v", err)} @@ -756,17 +784,17 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } if reqPrebid != nil { - aliases = reqPrebid.Aliases + requestAliases = reqPrebid.Aliases - if err := deps.validateAliases(aliases); err != nil { + if err := deps.validateAliases(requestAliases); err != nil { return []error{err} } - if err := deps.validateAliasesGVLIDs(reqPrebid.AliasGVLIDs, aliases); err != nil { + if err := deps.validateAliasesGVLIDs(reqPrebid.AliasGVLIDs, requestAliases); err != nil { return []error{err} } - if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil { + if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, requestAliases); err != nil { return []error{err} } @@ -774,7 +802,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return []error{err} } - if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { + if err := deps.validateEidPermissions(reqPrebid.Data, requestAliases); err != nil { return []error{err} } @@ -791,8 +819,8 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return []error{err} } - if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { - return append(errL, errors.New("request.site or request.app must be defined, but not both.")) + if err := validateExactlyOneInventoryType(req); err != nil { + return []error{err} } if errs := validateRequestExt(req); len(errs) != 0 { @@ -810,6 +838,9 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return append(errL, err) } + if err := deps.validateDOOH(req); err != nil { + return append(errL, err) + } var gpp gpplib.GppContainer if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) @@ -820,7 +851,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - if errs := deps.validateUser(req, aliases, gpp); errs != nil { + if errs := deps.validateUser(req, requestAliases, gpp); errs != nil { if len(errs) > 0 { errL = append(errL, errs...) } @@ -847,7 +878,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp if errortypes.ContainsFatalError([]error{err}) { return errL } - } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { + } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(requestAliases)); err != nil { if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { errL = append(errL, &errortypes.Warning{ Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), @@ -870,7 +901,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } impIDs[imp.ID] = i - errs := deps.validateImp(imp, aliases, i, hasStoredResponses, storedBidResp) + errs := deps.validateImp(imp, requestAliases, i, hasStoredResponses, storedBidResp) if len(errs) > 0 { errL = append(errL, errs...) } @@ -937,11 +968,25 @@ func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID } func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases map[string]string) error { + uniqueBidders := make(map[string]struct{}) for bidderToAdjust, adjustmentFactor := range adjustmentFactors { if adjustmentFactor <= 0 { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s must be a positive number. Got %f", bidderToAdjust, adjustmentFactor) } - if _, isBidder := deps.bidderMap[bidderToAdjust]; !isBidder { + + bidderName := bidderToAdjust + normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderToAdjust) + if ok { + bidderName = normalizedCoreBidder.String() + } + + if _, exists := uniqueBidders[bidderName]; exists { + return fmt.Errorf("cannot have multiple bidders that differ only in case style") + } else { + uniqueBidders[bidderName] = struct{}{} + } + + if _, isBidder := deps.bidderMap[bidderName]; !isBidder { if _, isAlias := aliases[bidderToAdjust]; !isAlias { return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s is not a known bidder or alias", bidderToAdjust) } @@ -955,7 +1000,7 @@ func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { return err } -func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { +func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, requestAliases map[string]string) error { if prebid == nil { return nil } @@ -975,7 +1020,7 @@ func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestP return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing or empty required field: "bidders"`, i) } - if err := validateBidders(eid.Bidders, deps.bidderMap, aliases); err != nil { + if err := deps.validateBidders(eid.Bidders, deps.bidderMap, requestAliases); err != nil { return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] contains %v`, i, err) } } @@ -983,15 +1028,16 @@ func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestP return nil } -func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownAliases map[string]string) error { +func (deps *endpointDeps) validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownRequestAliases map[string]string) error { for _, bidder := range bidders { if bidder == "*" { if len(bidders) > 1 { return errors.New(`bidder wildcard "*" mixed with specific bidders`) } } else { - _, isCoreBidder := knownBidders[bidder] - _, isAlias := knownAliases[bidder] + bidderNormalized, _ := deps.normalizeBidderName(bidder) + _, isCoreBidder := knownBidders[bidderNormalized.String()] + _, isAlias := knownRequestAliases[bidder] if !isCoreBidder && !isAlias { return fmt.Errorf(`unrecognized bidder "%v"`, bidder) } @@ -1152,7 +1198,7 @@ func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex) } var nativePayload nativeRequests.Request - if err := json.Unmarshal(json.RawMessage(n.Request), &nativePayload); err != nil { + if err := jsonutil.UnmarshalValid(json.RawMessage(n.Request), &nativePayload); err != nil { return err } @@ -1169,7 +1215,7 @@ func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { return err } - serialized, err := json.Marshal(nativePayload) + serialized, err := jsonutil.Marshal(nativePayload) if err != nil { return err } @@ -1488,21 +1534,19 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases ma return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} } - if len(storedBidResp) > 0 { - if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil { - return []error{err} - } + if err := deps.validateStoredBidRespAndImpExtBidders(prebid, storedBidResp, imp.ID); err != nil { + return []error{err} } errL := []error{} for bidder, ext := range prebid.Bidder { - coreBidder := bidder + coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) if tmp, isAlias := aliases[bidder]; isAlias { - coreBidder = tmp + coreBidder = openrtb_ext.BidderName(tmp) } - if coreBidderNormalized, isValid := deps.bidderMap[coreBidder]; isValid { + if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid { if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} } @@ -1562,18 +1606,21 @@ func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { - for alias, coreBidder := range aliases { - if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder) + for alias, bidderName := range aliases { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidderName) + coreBidderName := normalisedBidderName.String() + if _, isCoreBidderDisabled := deps.disabledBidders[coreBidderName]; isCoreBidderDisabled { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, bidderName) } - if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) + if _, isCoreBidder := deps.bidderMap[coreBidderName]; !isCoreBidder { + return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, bidderName) } - if alias == coreBidder { + if alias == coreBidderName { return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) } + aliases[alias] = coreBidderName } return nil } @@ -1734,6 +1781,18 @@ func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { return err } +func (deps *endpointDeps) validateDOOH(req *openrtb_ext.RequestWrapper) error { + if req.DOOH == nil { + return nil + } + + if req.DOOH.ID == "" && len(req.DOOH.VenueType) == 0 { + return errors.New("request.dooh should include at least one of request.dooh.id or request.dooh.venuetype.") + } + + return nil +} + func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string, gpp gpplib.GppContainer) []error { var errL []error @@ -1766,7 +1825,9 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases return append(errL, errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)) } for bidderName := range prebid.BuyerUIDs { - if _, ok := deps.bidderMap[bidderName]; !ok { + normalizedCoreBidder, _ := deps.normalizeBidderName(bidderName) + coreBidder := normalizedCoreBidder.String() + if _, ok := deps.bidderMap[coreBidder]; !ok { if _, ok := aliases[bidderName]; !ok { return append(errL, fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases", bidderName)) } @@ -1859,6 +1920,30 @@ func validateDevice(device *openrtb2.Device) error { return nil } +func validateExactlyOneInventoryType(reqWrapper *openrtb_ext.RequestWrapper) error { + + // Prep for mutual exclusion check + invTypeNumMatches := 0 + if reqWrapper.Site != nil { + invTypeNumMatches++ + } + if reqWrapper.App != nil { + invTypeNumMatches++ + } + if reqWrapper.DOOH != nil { + invTypeNumMatches++ + } + + if invTypeNumMatches == 0 { + return errors.New("One of request.site or request.app or request.dooh must be defined") + } else if invTypeNumMatches >= 2 { + return errors.New("No more than one of request.site or request.app or request.dooh can be defined") + } else { + return nil + } + +} + func validateOrFillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { requestExt, err := reqWrapper.GetRequestExt() if err != nil { @@ -1921,9 +2006,9 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator) - // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. If neither are - // present, we'll assume it's a site request. - if r.App == nil { + // Per the OpenRTB spec: A bid request must not contain more than one of Site|App|DOOH + // Assume it's a site request if it's not declared as one of the other values + if r.App == nil && r.DOOH == nil { setSiteImplicitly(httpReq, r) } @@ -1947,8 +2032,12 @@ func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) { } func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { + if r.Site == nil { + r.Site = &openrtb2.Site{} + } + referrerCandidate := httpReq.Referer() - if referrerCandidate == "" && r.Site != nil && r.Site.Page != "" { + if referrerCandidate == "" && r.Site.Page != "" { referrerCandidate = r.Site.Page // If http referer is disabled and thus has empty value - use site.page instead } @@ -1962,17 +2051,13 @@ func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { } } - if r.Site != nil { - if siteExt, err := r.GetSiteExt(); err == nil && siteExt.GetAmp() == nil { - siteExt.SetAmp(¬Amp) - } + if siteExt, err := r.GetSiteExt(); err == nil && siteExt.GetAmp() == nil { + siteExt.SetAmp(¬Amp) } + } func setSitePageIfEmpty(site *openrtb2.Site, sitePage string) { - if site == nil { - site = &openrtb2.Site{} - } if site.Page == "" { site.Page = sitePage } @@ -2002,7 +2087,7 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) { } type jNode map[string]*JsonNode docErrdoc := &jNode{} - docErr := json.Unmarshal(testJSON, docErrdoc) + docErr := jsonutil.UnmarshalValid(testJSON, docErrdoc) if uerror, ok := docErr.(*json.SyntaxError); ok { err := fmt.Sprintf("%s at offset %v", uerror.Error(), uerror.Offset) return true, err @@ -2151,7 +2236,7 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im } } if len(resolvedImps) > 0 { - newImpJson, err := json.Marshal(resolvedImps) + newImpJson, err := jsonutil.Marshal(resolvedImps) if err != nil { return nil, nil, []error{err} } @@ -2171,7 +2256,7 @@ func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") var impExtPrebid openrtb_ext.ExtImpPrebid if impExtData != nil { - if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { + if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { errs = append(errs, err) } } @@ -2275,7 +2360,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo metricsStatus := metrics.RequestStatusBadInput for _, err := range errs { erVal := errortypes.ReadCode(err) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable metricsStatus = metrics.RequestStatusBlacklisted break @@ -2300,7 +2385,7 @@ func getAccountID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher - err := json.Unmarshal(pub.Ext, &pubExt) + err := jsonutil.Unmarshal(pub.Ext, &pubExt) if err == nil && pubExt.Prebid != nil && pubExt.Prebid.ParentAccount != nil && *pubExt.Prebid.ParentAccount != "" { return *pubExt.Prebid.ParentAccount } @@ -2312,43 +2397,43 @@ func getAccountID(pub *openrtb2.Publisher) string { return metrics.PublisherUnknown } -func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, []error) { +func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, bool, []error) { request := originalRequest if hasStoredRequest { request = storedRequest } - accountId, isAppReq, err := searchAccountId(request) + accountId, isAppReq, isDOOHReq, err := searchAccountId(request) if err != nil { - return "", isAppReq, []error{err} + return "", isAppReq, isDOOHReq, []error{err} } // In case the stored request did not have account data we specifically search it in the original request if accountId == "" && hasStoredRequest { - accountId, _, err = searchAccountId(originalRequest) + accountId, _, _, err = searchAccountId(originalRequest) if err != nil { - return "", isAppReq, []error{err} + return "", isAppReq, isDOOHReq, []error{err} } } if accountId == "" { - return metrics.PublisherUnknown, isAppReq, nil + return metrics.PublisherUnknown, isAppReq, isDOOHReq, nil } - return accountId, isAppReq, nil + return accountId, isAppReq, isDOOHReq, nil } -func searchAccountId(request []byte) (string, bool, error) { +func searchAccountId(request []byte) (string, bool, bool, error) { for _, path := range accountIdSearchPath { accountId, exists, err := getStringValueFromRequest(request, path.key) if err != nil { - return "", path.isApp, err + return "", path.isApp, path.isDOOH, err } if exists { - return accountId, path.isApp, nil + return accountId, path.isApp, path.isDOOH, nil } } - return "", false, nil + return "", false, false, nil } func getStringValueFromRequest(request []byte, key []string) (string, bool, error) { @@ -2417,14 +2502,24 @@ func checkIfAppRequest(request []byte) (bool, error) { return false, nil } -func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { +func (deps *endpointDeps) validateStoredBidRespAndImpExtBidders(prebid *openrtb_ext.ExtImpPrebid, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { + if storedBidResp == nil && len(prebid.StoredBidResponse) == 0 { + return nil + } + + if storedBidResp == nil { + return generateStoredBidResponseValidationError(impId) + } if bidResponses, ok := storedBidResp[impId]; ok { - if len(bidResponses) != len(bidderExts) { + if len(bidResponses) != len(prebid.Bidder) { return generateStoredBidResponseValidationError(impId) } for bidderName := range bidResponses { - if _, present := bidderExts[bidderName]; !present { + if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk { + return fmt.Errorf(`unrecognized bidder "%v"`, bidderName) + } + if _, present := prebid.Bidder[bidderName]; !present { return generateStoredBidResponseValidationError(impId) } } @@ -2435,13 +2530,3 @@ func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage func generateStoredBidResponseValidationError(impID string) error { return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) } - -func recordResponsePreparationMetrics(mbti map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo, me metrics.MetricsEngine) { - for _, info := range mbti { - duration := time.Since(info.AfterMakeBidsStartTime) - for _, makeBidsDuration := range info.Durations { - duration += makeBidsDuration - } - me.RecordOverheadTime(metrics.MakeAuctionResponse, duration) - } -} diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index 9ad3c768a83..835d2920af4 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -10,17 +10,17 @@ import ( "testing" "time" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/macros" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/usersync" ) // benchmarkTestServer returns the header bidding test ad. This response was scraped from a real appnexus server response. @@ -95,6 +95,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) endpoint, _ := NewEndpoint( @@ -105,12 +106,13 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, nilMetrics, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, nil, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) b.ResetTimer() @@ -146,12 +148,10 @@ func BenchmarkValidWholeExemplary(b *testing.B) { test.endpointType = OPENRTB_ENDPOINT cfg := &config.Configuration{ - MaxRequestSize: maxSize, - BlacklistedApps: test.Config.BlacklistedApps, - BlacklistedAppMap: test.Config.getBlacklistedAppMap(), - BlacklistedAccts: test.Config.BlacklistedAccounts, - BlacklistedAcctMap: test.Config.getBlackListedAccountMap(), - AccountRequired: test.Config.AccountRequired, + MaxRequestSize: maxSize, + BlacklistedApps: test.Config.BlacklistedApps, + BlacklistedAppMap: test.Config.getBlacklistedAppMap(), + AccountRequired: test.Config.AccountRequired, } auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index e277411b7ef..c521d653cac 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -22,24 +22,23 @@ import ( "github.com/prebid/openrtb/v19/native1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) const jsonFileExtension string = ".json" @@ -162,8 +161,6 @@ func runJsonBasedTest(t *testing.T, filename, desc string) { if test.Config != nil { cfg.BlacklistedApps = test.Config.BlacklistedApps cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() - cfg.BlacklistedAccts = test.Config.BlacklistedAccounts - cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() cfg.AccountRequired = test.Config.AccountRequired } cfg.MarshalAccountDefaults() @@ -196,7 +193,7 @@ func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, tes // Either assert bid response or expected error if len(test.ExpectedErrorMessage) > 0 { - assert.True(t, strings.HasPrefix(actualJsonBidResponse, test.ExpectedErrorMessage), "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile) + assert.Contains(t, actualJsonBidResponse, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile) } if len(test.ExpectedBidResponse) > 0 { @@ -204,10 +201,10 @@ func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, tes var actualBidResponse openrtb2.BidResponse var err error - err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) + err = jsonutil.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) if assert.NoError(t, err, "Could not unmarshal expected bidResponse taken from test file.\n Test file: %s\n Error:%s\n", testFile, err) { - err = json.Unmarshal([]byte(actualJsonBidResponse), &actualBidResponse) - if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n", testFile, err) { + err = jsonutil.UnmarshalValid([]byte(actualJsonBidResponse), &actualBidResponse) + if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n actualJsonBidResponse: %s", testFile, err, actualJsonBidResponse) { assertBidResponseEqual(t, testFile, expectedBidResponse, actualBidResponse) } } @@ -226,13 +223,13 @@ func compareWarnings(t *testing.T, expectedBidResponseExt, actualBidResponseExt } var expectedWarn []openrtb_ext.ExtBidderMessage - err = json.Unmarshal(expectedWarnings, &expectedWarn) + err = jsonutil.UnmarshalValid(expectedWarnings, &expectedWarn) if err != nil { assert.Fail(t, "error unmarshalling expected warnings data from response extension") } var actualWarn []openrtb_ext.ExtBidderMessage - err = json.Unmarshal(actualWarnings, &actualWarn) + err = jsonutil.UnmarshalValid(actualWarnings, &actualWarn) if err != nil { assert.Fail(t, "error unmarshalling actual warnings data from response extension") } @@ -451,12 +448,14 @@ func TestExplicitUserId(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) endpoint(httptest.NewRecorder(), request, nil) @@ -478,8 +477,8 @@ func TestExplicitUserId(t *testing.T) { // processes aliases before it processes stored imps. Changing that order // would probably cause this test to fail. func TestBadAliasRequests(t *testing.T) { - doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON in Default Request Settings: invalid character '\"' after object key:value pair at offset 51\n") - doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON in Incoming Request: invalid character '\"' after object key:value pair at offset 230\n") + doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON Document\n") + doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON Document\n") } // doBadAliasRequest() is a customized variation of doRequest(), above @@ -495,7 +494,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { bidderInfos := getBidderInfos(nil, openrtb_ext.CoreBidderNames()) bidderMap := exchange.GetActiveBidders(bidderInfos) - disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. @@ -507,12 +506,14 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap, empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest)) recorder := httptest.NewRecorder() @@ -560,11 +561,13 @@ func TestNilExchange(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil Exchange.") @@ -583,12 +586,14 @@ func TestNilValidator(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) if err == nil { t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.") @@ -607,12 +612,14 @@ func TestExchangeError(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -732,12 +739,14 @@ func TestImplicitIPsEndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader) @@ -930,12 +939,14 @@ func TestImplicitDNTEndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) httpReq.Header.Set("DNT", test.dntHeader) @@ -953,19 +964,22 @@ func TestImplicitDNTEndToEnd(t *testing.T) { func TestReferer(t *testing.T) { testCases := []struct { description string - givenSitePage string - givenSiteDomain string - givenPublisherDomain string givenReferer string expectedDomain string expectedPage string expectedPublisherDomain string + bidReq *openrtb_ext.RequestWrapper }{ { description: "site.page/domain are unchanged when site.page/domain and http referer are not set", expectedDomain: "", expectedPage: "", expectedPublisherDomain: "", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{}, + }, + }}, }, { description: "site.page/domain are derived from referer when neither is set and http referer is set", @@ -973,39 +987,73 @@ func TestReferer(t *testing.T) { expectedDomain: "test.somepage.com", expectedPublisherDomain: "somepage.com", expectedPage: "https://test.somepage.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{}, + }, + }}, }, { description: "site.domain is derived from site.page when site.page is set and http referer is not set", - givenSitePage: "https://test.somepage.com", expectedDomain: "test.somepage.com", expectedPublisherDomain: "somepage.com", expectedPage: "https://test.somepage.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "https://test.somepage.com", + Publisher: &openrtb2.Publisher{}, + }, + }}, }, { description: "site.domain is derived from http referer when site.page and http referer are set", - givenSitePage: "https://test.somepage.com", givenReferer: "http://test.com", expectedDomain: "test.com", expectedPublisherDomain: "test.com", expectedPage: "https://test.somepage.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "https://test.somepage.com", + Publisher: &openrtb2.Publisher{}, + }, + }}, }, { description: "site.page/domain are unchanged when site.page/domain and http referer are set", - givenSitePage: "https://test.somepage.com", - givenSiteDomain: "some.domain.com", givenReferer: "http://test.com", expectedDomain: "some.domain.com", expectedPublisherDomain: "test.com", expectedPage: "https://test.somepage.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Domain: "some.domain.com", + Page: "https://test.somepage.com", + Publisher: &openrtb2.Publisher{}, + }, + }}, }, { description: "Publisher domain shouldn't be overrwriten if already set", - givenSitePage: "https://test.somepage.com", - givenSiteDomain: "", - givenPublisherDomain: "differentpage.com", expectedDomain: "test.somepage.com", expectedPublisherDomain: "differentpage.com", expectedPage: "https://test.somepage.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Domain: "", + Page: "https://test.somepage.com", + Publisher: &openrtb2.Publisher{ + Domain: "differentpage.com", + }, + }, + }}, + }, + { + description: "request.site is nil", + givenReferer: "http://test.com", + expectedDomain: "test.com", + expectedPublisherDomain: "test.com", + expectedPage: "http://test.com", + bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, }, } @@ -1013,20 +1061,12 @@ func TestReferer(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("Referer", test.givenReferer) - bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ - Site: &openrtb2.Site{ - Domain: test.givenSiteDomain, - Page: test.givenSitePage, - Publisher: &openrtb2.Publisher{Domain: test.givenPublisherDomain}, - }, - }} - - setSiteImplicitly(httpReq, bidReq) + setSiteImplicitly(httpReq, test.bidReq) - assert.NotNil(t, bidReq.Site, test.description) - assert.Equal(t, test.expectedDomain, bidReq.Site.Domain, test.description) - assert.Equal(t, test.expectedPage, bidReq.Site.Page, test.description) - assert.Equal(t, test.expectedPublisherDomain, bidReq.Site.Publisher.Domain, test.description) + assert.NotNil(t, test.bidReq.Site, test.description) + assert.Equal(t, test.expectedDomain, test.bidReq.Site.Domain, test.description) + assert.Equal(t, test.expectedPage, test.bidReq.Site.Page, test.description) + assert.Equal(t, test.expectedPublisherDomain, test.bidReq.Site.Publisher.Domain, test.description) } } @@ -1136,7 +1176,7 @@ func TestStoredRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1146,6 +1186,8 @@ func TestStoredRequests(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } testStoreVideoAttr := []bool{true, true, false, false, false} @@ -1499,6 +1541,81 @@ func TestMergeBidderParamsImpExtPrebid(t *testing.T) { } } +func TestValidateExactlyOneInventoryType(t *testing.T) { + + testCases := []struct { + description string + givenRequestWrapper *openrtb_ext.RequestWrapper + expectedError error + }{ + { + description: "None provided - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + expectedError: errors.New("One of request.site or request.app or request.dooh must be defined"), + }, + { + description: "Only site provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + }}, + expectedError: nil, + }, + { + description: "Only app provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + }}, + expectedError: nil, + }, + { + description: "Only dooh provided", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: nil, + }, + { + description: "Two provided (site+app) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + App: &openrtb2.App{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Two provided (site+dooh) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Two provided (app+dooh) - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + { + description: "Three provided - invalid", + givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + App: &openrtb2.App{}, + DOOH: &openrtb2.DOOH{}, + }}, + expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"), + }, + } + + for _, test := range testCases { + error := validateExactlyOneInventoryType(test.givenRequestWrapper) + assert.Equalf(t, test.expectedError, error, "Error doesn't match: %s\n", test.description) + } + +} + func TestValidateRequest(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, @@ -1509,7 +1626,7 @@ func TestValidateRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1519,6 +1636,8 @@ func TestValidateRequest(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -1613,7 +1732,7 @@ func TestValidateRequest(t *testing.T) { Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), }, }, - Ext: []byte(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids":{"pubmatic1":1}}}`), + Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"pubmatic1":1}}}`), }, }, givenIsAmp: false, @@ -1644,11 +1763,11 @@ func TestValidateRequest(t *testing.T) { Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), }, }, - Ext: []byte(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids":{"brightroll":0}}}`), + Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":0}}}`), }, }, givenIsAmp: false, - expectedErrorList: []error{errors.New("request.ext.prebid.aliasgvlids. Invalid vendorId 0 for alias: brightroll. Choose a different vendorId, or remove this entry.")}, + expectedErrorList: []error{errors.New("request.ext.prebid.aliasgvlids. Invalid vendorId 0 for alias: yahoossp. Choose a different vendorId, or remove this entry.")}, expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, }, { @@ -1675,13 +1794,73 @@ func TestValidateRequest(t *testing.T) { Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), }, }, - Ext: []byte(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids":{"brightroll":1}}}`), + Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":1}}}`), }, }, givenIsAmp: false, expectedErrorList: []error{}, expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, }, + { + description: "Minimum required site attributes missing", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + Site: &openrtb2.Site{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + }, + }, + expectedErrorList: []error{ + errors.New("request.site should include at least one of request.site.id or request.site.page."), + }, + }, + { + description: "Minimum required DOOH attributes missing", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + DOOH: &openrtb2.DOOH{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), + }, + }, + }, + }, + expectedErrorList: []error{ + errors.New("request.dooh should include at least one of request.dooh.id or request.dooh.venuetype."), + }, + }, } for _, test := range testCases { @@ -1729,7 +1908,7 @@ func TestValidateRequestExt(t *testing.T) { { description: "prebid cache - bids - wrong type", givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":true}}}`), - expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`}, + expectedErrors: []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t"}, }, { description: "prebid cache - bids - provided", @@ -1743,7 +1922,7 @@ func TestValidateRequestExt(t *testing.T) { { description: "prebid cache - vastxml - wrong type", givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":true}}}`), - expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`}, + expectedErrors: []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.VastXML: expect { or n, but found t"}, }, { description: "prebid cache - vastxml - provided", @@ -2227,7 +2406,7 @@ func TestSetIntegrationType(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2237,6 +2416,8 @@ func TestSetIntegrationType(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -2292,7 +2473,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2302,6 +2483,8 @@ func TestStoredRequestGenerateUuid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } req := &openrtb2.BidRequest{} @@ -2373,7 +2556,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { newRequest, _, errList := deps.processStoredRequests(json.RawMessage(test.givenRawData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) assert.Empty(t, errList, test.description) - if err := json.Unmarshal(newRequest, req); err != nil { + if err := jsonutil.UnmarshalValid(newRequest, req); err != nil { t.Errorf("processStoredRequests Error: %s", err.Error()) } if test.expectedCur != "" { @@ -2395,7 +2578,7 @@ func TestOversizedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2405,6 +2588,8 @@ func TestOversizedRequest(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -2433,7 +2618,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2443,6 +2628,8 @@ func TestRequestSizeEdgeCase(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) @@ -2469,12 +2656,13 @@ func TestNoEncoding(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -2553,12 +2741,13 @@ func TestContentType(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, ) request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) recorder := httptest.NewRecorder() @@ -2769,7 +2958,7 @@ func TestValidateImpExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(8096)}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, false, []byte{}, @@ -2779,23 +2968,27 @@ func TestValidateImpExt(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } for _, group := range testGroups { for _, test := range group.testCases { - imp := &openrtb2.Imp{Ext: test.impExt} - impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} + t.Run(test.description, func(t *testing.T) { + imp := &openrtb2.Imp{Ext: test.impExt} + impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} - errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) + errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) - assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") + assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") - if len(test.expectedImpExt) > 0 { - assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) - } else { - assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) - } - assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) + } else { + assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) + } + assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + }) } } } @@ -2821,7 +3014,7 @@ func TestCurrencyTrunc(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2831,6 +3024,8 @@ func TestCurrencyTrunc(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -2868,7 +3063,7 @@ func TestCCPAInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2878,6 +3073,8 @@ func TestCCPAInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -2919,7 +3116,7 @@ func TestNoSaleInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2929,6 +3126,8 @@ func TestNoSaleInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -2973,7 +3172,7 @@ func TestValidateSourceTID(t *testing.T) { empty_fetcher.EmptyFetcher{}, cfg, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -2983,6 +3182,8 @@ func TestValidateSourceTID(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3017,7 +3218,7 @@ func TestSChainInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3027,6 +3228,8 @@ func TestSChainInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3182,6 +3385,78 @@ func TestMapSChains(t *testing.T) { } } +func TestSearchAccountID(t *testing.T) { + // Correctness for lookup within Publisher object left to TestGetAccountID + // This however tests the expected lookup paths in outer site, app and dooh + testCases := []struct { + description string + request []byte + expectedAccID string + expectedError error + expectedIsAppReq bool + expectedIsSiteReq bool + expectedIsDOOHReq bool + }{ + { + description: "No publisher available", + request: []byte(`{}`), + expectedAccID: "", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher.ID doesn't exist", + request: []byte(`{"site":{"publisher":{}}}`), + expectedAccID: "", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher.ID not a string", + request: []byte(`{"site":{"publisher":{"id":42}}}`), + expectedAccID: "", + expectedError: errors.New("site.publisher.id must be a string"), + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.site", + request: []byte(`{"site":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.app", + request: []byte(`{"app":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: true, + expectedIsDOOHReq: false, + }, + { + description: "Publisher available in request.dooh", + request: []byte(`{"dooh":{"publisher":{"id":"42"}}}`), + expectedAccID: "42", + expectedError: nil, + expectedIsAppReq: false, + expectedIsDOOHReq: true, + }, + } + + for _, test := range testCases { + accountId, isAppReq, isDOOHReq, err := searchAccountId(test.request) + assert.Equal(t, test.expectedAccID, accountId, "searchAccountID should return expected account ID for test case: %s", test.description) + assert.Equal(t, test.expectedIsAppReq, isAppReq, "searchAccountID should return expected isAppReq for test case: %s", test.description) + assert.Equal(t, test.expectedIsDOOHReq, isDOOHReq, "searchAccountID should return expected isDOOHReq for test case: %s", test.description) + assert.Equal(t, test.expectedError, err, "searchAccountID should return expected error for test case: %s", test.description) + } + +} + func TestGetAccountID(t *testing.T) { testPubID := "test-pub" testParentAccount := "test-account" @@ -3190,7 +3465,7 @@ func TestGetAccountID(t *testing.T) { ParentAccount: &testParentAccount, }, } - testPubExtJSON, err := json.Marshal(testPubExt) + testPubExtJSON, err := jsonutil.Marshal(testPubExt) assert.NoError(t, err) testCases := []struct { @@ -3512,7 +3787,7 @@ func TestEidPermissionsInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3522,6 +3797,8 @@ func TestEidPermissionsInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } ui := int64(1) @@ -3590,6 +3867,13 @@ func TestValidateEidPermissions(t *testing.T) { }}}}, expectedError: nil, }, + { + description: "Valid - One - Case Insensitive", + request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "sourceA", Bidders: []string{"A"}}, + }}}}, + expectedError: nil, + }, { description: "Valid - Many", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ @@ -3638,9 +3922,16 @@ func TestValidateEidPermissions(t *testing.T) { }}}}, expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`), }, + { + description: "Valid - Alias Case Sensitive", + request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "sourceA", Bidders: []string{"B"}}, + }}}}, + expectedError: errors.New(`request.ext.prebid.data.eidpermissions[0] contains unrecognized bidder "B"`), + }, } - endpoint := &endpointDeps{bidderMap: knownBidders} + endpoint := &endpointDeps{bidderMap: knownBidders, normalizeBidderName: fakeNormalizeBidderName} for _, test := range testCases { result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) assert.Equal(t, test.expectedError, result, test.description) @@ -3676,6 +3967,13 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: nil, }, + { + description: "Valid - One Core Bidder - Case Insensitive", + bidders: []string{"A"}, + knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, + knownAliases: map[string]string{"c": "c"}, + expectedError: nil, + }, { description: "Valid - Many Core Bidders", bidders: []string{"a", "b"}, @@ -3690,6 +3988,13 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: nil, }, + { + description: "Valid - One Alias Bidder - Case Sensitive", + bidders: []string{"C"}, + knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, + knownAliases: map[string]string{"c": "c"}, + expectedError: errors.New(`unrecognized bidder "C"`), + }, { description: "Valid - Many Alias Bidders", bidders: []string{"c", "d"}, @@ -3711,13 +4016,6 @@ func TestValidateBidders(t *testing.T) { knownAliases: map[string]string{"c": "c"}, expectedError: errors.New(`unrecognized bidder "z"`), }, - { - description: "Invalid - Unknown Bidder Case Sensitive", - bidders: []string{"A"}, - knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, - knownAliases: map[string]string{"c": "c"}, - expectedError: errors.New(`unrecognized bidder "A"`), - }, { description: "Invalid - Unknown Bidder With Known Bidders", bidders: []string{"a", "c", "z"}, @@ -3748,8 +4046,9 @@ func TestValidateBidders(t *testing.T) { }, } + endpoint := &endpointDeps{normalizeBidderName: fakeNormalizeBidderName} for _, test := range testCases { - result := validateBidders(test.bidders, test.knownBidders, test.knownAliases) + result := endpoint.validateBidders(test.bidders, test.knownBidders, test.knownAliases) assert.Equal(t, test.expectedError, result, test.description) } } @@ -3765,12 +4064,14 @@ func TestIOS14EndToEnd(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json"))) @@ -3826,7 +4127,7 @@ func TestAuctionWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3836,6 +4137,8 @@ func TestAuctionWarnings(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { @@ -3871,7 +4174,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3881,6 +4184,8 @@ func TestParseRequestParseImpInfoError(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -3892,7 +4197,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { assert.Nil(t, resReq, "Result request should be nil due to incorrect imp") assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp") assert.Len(t, errL, 1, "One error should be returned") - assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") + assert.Contains(t, errL[0].Error(), "cannot unmarshal openrtb_ext.Options.EchoVideoAttrs", "Incorrect error message") } func TestParseGzipedRequest(t *testing.T) { @@ -3950,7 +4255,7 @@ func TestParseGzipedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(50), Compression: config.Compression{Request: config.CompressionInfo{GZIP: false}}}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -3960,6 +4265,8 @@ func TestParseGzipedRequest(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4448,12 +4755,14 @@ func TestAuctionResponseHeaders(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap(), empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}) + hooks.EmptyPlanBuilder{}, + nil, + ) for _, test := range testCases { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody)) @@ -4547,7 +4856,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4557,6 +4866,8 @@ func TestParseRequestMergeBidderParams(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4568,20 +4879,20 @@ func TestParseRequestMergeBidderParams(t *testing.T) { assert.NoError(t, resReq.RebuildRequest()) var expIExt, iExt map[string]interface{} - err := json.Unmarshal(test.expectedImpExt, &expIExt) + err := jsonutil.UnmarshalValid(test.expectedImpExt, &expIExt) assert.Nil(t, err, "unmarshal() should return nil error") assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil") - err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt) + err = jsonutil.UnmarshalValid(resReq.BidRequest.Imp[0].Ext, &iExt) assert.Nil(t, err, "unmarshal() should return nil error") assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match") var eReqE, reqE map[string]interface{} - err = json.Unmarshal(test.expectedReqExt, &eReqE) + err = jsonutil.UnmarshalValid(test.expectedReqExt, &eReqE) assert.Nil(t, err, "unmarshal() should return nil error") - err = json.Unmarshal(resReq.BidRequest.Ext, &reqE) + err = jsonutil.UnmarshalValid(resReq.BidRequest.Ext, &reqE) assert.Nil(t, err, "unmarshal() should return nil error") assert.Equal(t, eReqE, reqE, "req.Ext should match") @@ -4649,7 +4960,7 @@ func TestParseRequestStoredResponses(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4659,6 +4970,8 @@ func TestParseRequestStoredResponses(t *testing.T) { hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{mockStoredResponses}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -4678,11 +4991,13 @@ func TestParseRequestStoredResponses(t *testing.T) { } func TestParseRequestStoredBidResponses(t *testing.T) { - bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) - bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) + bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "telaria"}], "bidid": "123", "cur": "USD"}`) + bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "amx"}], "bidid": "124", "cur": "USD"}`) + bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`) mockStoredBidResponses := map[string]json.RawMessage{ "bidResponseId1": bidRespId1, "bidResponseId2": bidRespId2, + "bidResponseId3": bidRespId3, } tests := []struct { @@ -4696,7 +5011,23 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req imp has valid stored bid response", givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, + "imp-id1": {"telaria": bidRespId1}, + }, + expectedErrorCount: 0, + }, + { + name: "req imp has valid stored bid response with case not-matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-not-matching-bidder-name.json"), + expectedStoredBidResponses: map[string]map[string]json.RawMessage{ + "imp-id3": {"appnexus": bidRespId3}, + }, + expectedErrorCount: 0, + }, + { + name: "req imp has valid stored bid response with case matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-matching-bidder-name.json"), + expectedStoredBidResponses: map[string]map[string]json.RawMessage{ + "imp-id3": {"appnexus": bidRespId3}, }, expectedErrorCount: 0, }, @@ -4704,8 +5035,8 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with valid stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id1": {"telaria": bidRespId1}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -4713,7 +5044,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps one with valid stored bid responses and another one without stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -4721,7 +5052,13 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with missing stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"), expectedStoredBidResponses: nil, - expectedErrorCount: 2, + expectedErrorCount: 1, + }, + { + name: "req imp has valid stored bid response with non existing bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-non-existing-bidder-name.json"), + expectedStoredBidResponses: nil, + expectedErrorCount: 1, }, } for _, test := range tests { @@ -4736,24 +5073,26 @@ func TestParseRequestStoredBidResponses(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, - map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2"}, + map[string]openrtb_ext.BidderName{"telaria": "telaria", "amx": "amx", "appnexus": "appnexus"}, nil, nil, hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{mockStoredBidResponses}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) - if test.expectedErrorCount == 0 { + assert.Empty(t, errL) assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") } else { assert.Contains(t, errL[0].Error(), test.expectedError, "error should match") @@ -4772,7 +5111,7 @@ func TestValidateStoredResp(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -4782,6 +5121,8 @@ func TestValidateStoredResp(t *testing.T) { hardcodedResponseIPValidator{response: true}, &mockStoredResponseFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } testCases := []struct { @@ -5327,8 +5668,10 @@ func TestValidateStoredResp(t *testing.T) { } for _, test := range testCases { - errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } @@ -5428,11 +5771,11 @@ func TestValidResponseAfterExecutingStages(t *testing.T) { var actualExt openrtb_ext.ExtBidResponse var expectedExt openrtb_ext.ExtBidResponse - assert.NoError(t, json.Unmarshal(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") - assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") if expectedResp.Ext != nil { - assert.NoError(t, json.Unmarshal(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") - assert.NoError(t, json.Unmarshal(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") + assert.NoError(t, jsonutil.UnmarshalValid(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") } assertBidResponseEqual(t, tc.file, expectedResp, actualResp) @@ -5518,7 +5861,7 @@ func TestSendAuctionResponse_LogsErrors(t *testing.T) { ao := analytics.AuctionObject{} account := &config.Account{DebugAllow: true} - labels, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) + _, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") @@ -5584,7 +5927,7 @@ func TestParseRequestMultiBid(t *testing.T) { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -5594,6 +5937,8 @@ func TestParseRequestMultiBid(t *testing.T) { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) @@ -5632,7 +5977,7 @@ func getObject(t *testing.T, filename, key string) json.RawMessage { assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) var obj json.RawMessage - err = json.Unmarshal(testBidRequest, &obj) + err = jsonutil.UnmarshalValid(testBidRequest, &obj) if err != nil { t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err) } @@ -5658,15 +6003,6 @@ func (e mockStageExecutor) GetOutcomes() []hookexecution.StageOutcome { return e.outcomes } -func TestRecordResponsePreparationMetrics(t *testing.T) { - mbi := map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo{ - openrtb_ext.BidderAppnexus: {Durations: []time.Duration{10, 15}, AfterMakeBidsStartTime: time.Now()}, - } - mockMetricEngine := &metrics.MetricsEngineMock{} - mockMetricEngine.On("RecordOverheadTime", metrics.MakeAuctionResponse, mock.Anything) - recordResponsePreparationMetrics(mbi, mockMetricEngine) -} - func TestSetSeatNonBidRaw(t *testing.T) { type args struct { request *openrtb_ext.RequestWrapper @@ -5712,3 +6048,63 @@ func TestSetSeatNonBidRaw(t *testing.T) { }) } } + +func TestValidateAliases(t *testing.T) { + deps := &endpointDeps{ + disabledBidders: map[string]string{"rubicon": "rubicon"}, + bidderMap: map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderName("appnexus")}, + } + + testCases := []struct { + description string + aliases map[string]string + expectedAliases map[string]string + expectedError error + }{ + { + description: "valid case", + aliases: map[string]string{"test": "appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "valid case - case insensitive", + aliases: map[string]string{"test": "Appnexus"}, + expectedAliases: map[string]string{"test": "appnexus"}, + expectedError: nil, + }, + { + description: "disabled bidder", + aliases: map[string]string{"test": "rubicon"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to disabled bidder: rubicon"), + }, + { + description: "coreBidderName not found", + aliases: map[string]string{"test": "anyBidder"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.test refers to unknown bidder: anyBidder"), + }, + { + description: "alias name is coreBidder name", + aliases: map[string]string{"appnexus": "appnexus"}, + expectedAliases: nil, + expectedError: errors.New("request.ext.prebid.aliases.appnexus defines a no-op alias. Choose a different alias, or remove this entry."), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + err := deps.validateAliases(testCase.aliases) + if err != nil { + assert.Equal(t, testCase.expectedError, err) + } else { + assert.ObjectsAreEqualValues(testCase.expectedAliases, map[string]string{"test": "appnexus"}) + } + }) + } +} + +func fakeNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(strings.ToLower(name)), true +} diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 46dc7a61510..330119b6f8f 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func processInterstitials(req *openrtb_ext.RequestWrapper) error { diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 947817803b2..eb69bc91f08 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json deleted file mode 100644 index 894a92fdb27..00000000000 --- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-blacklisted-acct.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "description": "Account is required but request comes with a blacklisted account id", - "config": { - "accountRequired": true, - "blacklistedAccts": ["bad_acct"] - }, - "mockBidRequest": { - "id": "some-request-id", - "user": { - "ext": { - "consent": "gdpr-consent-string", - "prebid": { - "buyeruids": { - "appnexus": "override-appnexus-id-in-cookie" - } - } - } - }, - "app": { - "id": "cool_app", - "publisher": { - "id": "bad_acct" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "some-impression-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "districtm": { - "placementId": 105 - }, - "rubicon": { - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - } - } - } - ], - "tmax": 500, - "ext": { - "prebid": { - "aliases": { - "districtm": "appnexus" - }, - "bidadjustmentfactors": { - "appnexus": 1.01, - "districtm": 0.98, - "rubicon": 0.99 - }, - "cache": { - "bids": {} - }, - "targeting": { - "includewinners": false, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "max": 20, - "increment": 0.10 - } - ] - } - } - } - } - }, - "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" -} diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json similarity index 86% rename from endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json rename to endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json index ef7a93b8bad..31a0021b7c1 100644 --- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app-publisher.json +++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-disabled-acct.json @@ -1,7 +1,7 @@ { - "description": "This is a perfectly valid request except that it comes with a blacklisted app publisher account", + "description": "Account is required but request comes with a disabled account id", "config": { - "blacklistedAccts": ["bad_acct"] + "accountRequired": true }, "mockBidRequest": { "id": "some-request-id", @@ -18,7 +18,7 @@ "app": { "id": "cool_app", "publisher": { - "id": "bad_acct" + "id": "disabled_acct" } }, "regs": { @@ -86,5 +86,5 @@ } }, "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" + "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: disabled_acct, please reach out to the prebid server host.\n" } diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json new file mode 100644 index 00000000000..1d2462c98b8 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/aliased-buyeruids-case-insensitive.json @@ -0,0 +1,84 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field makes use of alias defined in root.ext.prebid.aliases and request makes use of case sensitive bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "Appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "unknown": "123" + } + } + } + }, + "ext": { + "prebid": { + "aliases": { + "unknown": "Appnexus" + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "Appnexus", + "hb_bidder_Appnexus": "Appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_Appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_Appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_Appnex": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_Appnexus": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json new file mode 100644 index 00000000000..4597fe7786a --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-camel-case.json @@ -0,0 +1,77 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field has camel case bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "yahooAds", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "YahooAds": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "YahooAds": "123" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "YahooAds", + "hb_bidder_YahooAds": "YahooAds", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_YahooA": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_YahooAds": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_YahooA": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_YahooAds": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json new file mode 100644 index 00000000000..976fcb3cf7f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/buyeruids-case-insensitive.json @@ -0,0 +1,77 @@ +{ + "description": "Amp request where root.user.ext.prebid.buyeruids field has case insensitive bidder name", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "request-with-user-ext-obj", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "Appnexus": { + "placementId": 12883451 + } + } + } + } + } + ], + "user": { + "ext": { + "prebid": { + "buyeruids": { + "Appnexus": "123" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "Appnexus", + "hb_bidder_Appnexus": "Appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_Appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_Appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_Appnex": "/pbcache/endpoint", + "hb_pb": "2.00", + "hb_pb_Appnexus": "2.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json index c3ce226ff9c..7fc1e7d8721 100644 --- a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_reject.json @@ -4,7 +4,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 2}, - {"bidderName": "applogy", "currency": "USD", "price": 1} + {"bidderName": "rubicon", "currency": "USD", "price": 1} ] }, "mockBidRequest": { @@ -29,7 +29,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -42,23 +42,23 @@ "debug": true, "aliases": { "unknown": "appnexus", - "foo": "applogy" + "foo": "rubicon" } } } }, "expectedAmpResponse": { "targeting": { - "hb_bidder": "applogy", - "hb_bidder_applogy": "applogy", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_applog": "www.pbcserver.com", + "hb_cache_host_rubico": "www.pbcserver.com", "hb_cache_id": "0", - "hb_cache_id_applogy": "0", + "hb_cache_id_rubicon": "0", "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_applog": "/pbcache/endpoint", + "hb_cache_path_rubico": "/pbcache/endpoint", "hb_pb": "1.00", - "hb_pb_applogy": "1.00" + "hb_pb_rubicon": "1.00" }, "ortb2": { "ext": { @@ -103,7 +103,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json index fe654304c4f..4e38b5a2f2d 100644 --- a/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/amp_bidder_response_reject.json @@ -4,7 +4,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 2}, - {"bidderName": "applogy", "currency": "USD", "price": 1} + {"bidderName": "rubicon", "currency": "USD", "price": 1} ] }, "mockBidRequest": { @@ -29,7 +29,7 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } @@ -42,23 +42,23 @@ "debug": true, "aliases": { "unknown": "appnexus", - "foo": "applogy" + "foo": "rubicon" } } } }, "expectedAmpResponse": { "targeting": { - "hb_bidder": "applogy", - "hb_bidder_applogy": "applogy", + "hb_bidder": "rubicon", + "hb_bidder_rubicon": "rubicon", "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_applog": "www.pbcserver.com", + "hb_cache_host_rubico": "www.pbcserver.com", "hb_cache_id": "0", - "hb_cache_id_applogy": "0", + "hb_cache_id_rubicon": "0", "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_applog": "/pbcache/endpoint", + "hb_cache_path_rubico": "/pbcache/endpoint", "hb_pb": "1.00", - "hb_pb_applogy": "1.00" + "hb_pb_rubicon": "1.00" }, "ortb2": { "ext": { @@ -103,7 +103,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json index bece42277ef..0bc86e4c4a5 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json @@ -3,7 +3,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 0.00}, - {"bidderName": "applogy", "currency": "USD", "price": 0.00} + {"bidderName": "rubicon", "currency": "USD", "price": 0.00} ] }, "mockBidRequest": { @@ -26,13 +26,13 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } } ], - "tmax": 50, + "tmax": 500, "test": 1, "ext": { "prebid": { @@ -49,12 +49,12 @@ { "bid": [ { - "id": "applogy-bid", + "id": "rubicon-bid", "impid": "some-impression-id", "price": 0 } ], - "seat": "applogy" + "seat": "rubicon" } ], "ext": { @@ -99,7 +99,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json index 32743d67b75..16a7f43c87a 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json @@ -3,7 +3,7 @@ "config": { "mockBidders": [ {"bidderName": "appnexus", "currency": "USD", "price": 0.00}, - {"bidderName": "applogy", "currency": "USD", "price": 0.00} + {"bidderName": "rubicon", "currency": "USD", "price": 0.00} ] }, "mockBidRequest": { @@ -26,13 +26,13 @@ "appnexus": { "placementId": 12883451 }, - "applogy": { + "rubicon": { "placementId": 12883451 } } } ], - "tmax": 50, + "tmax": 500, "test": 1, "ext": { "prebid": { @@ -49,12 +49,12 @@ { "bid": [ { - "id": "applogy-bid", + "id": "rubicon-bid", "impid": "some-impression-id", "price": 0 } ], - "seat": "applogy" + "seat": "rubicon" } ], "ext": { @@ -99,7 +99,7 @@ ] }, { - "entity": "applogy", + "entity": "rubicon", "groups": [ { "invocation_results": [ diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json index c67b8d4b490..3481b986bc3 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json index eca136cf75f..4c160dec007 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json index dc192baffee..ed284e6687d 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json index 812f0664fbb..cf699fea0c9 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_1.json @@ -11,5 +11,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: Invalid JSON in Incoming Request: invalid character ']' looking for beginning of value at offset" + "expectedErrorMessage": "Invalid request: expect { or n, but found" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json index e3960b17399..fb0dc866d10 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_incoming_imp.json @@ -37,5 +37,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: Invalid JSON in Imp[0] of Incoming Request: invalid character '\"' after object key:value pair at offset 132\n" + "expectedErrorMessage": "Invalid request: Invalid JSON Document" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json index 0fed6c32adf..ea19855e75a 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_imp.json @@ -33,5 +33,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: imp.ext.prebid.storedrequest.id 7: Stored Imp has Invalid JSON" + "expectedErrorMessage": "Invalid request: Invalid JSON Document" } diff --git a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json index 4e9d7f03352..235eb26179b 100644 --- a/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json +++ b/endpoints/openrtb2/sample-requests/invalid-stored/bad_stored_req.json @@ -23,5 +23,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: ext.prebid.storedrequest.id refers to Stored Request 3 which contains Invalid JSON" + "expectedErrorMessage": "Invalid request: expect { or n, but found" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json b/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json new file mode 100644 index 00000000000..d484f129ba3 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/bid-adj-factors-case-invalid-same-bidder-names.json @@ -0,0 +1,81 @@ +{ + "description": "This demonstrates a request with bidadjustmentfactors that have multiple of the same bidder is invalid request", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustmentfactors": { + "APPNEXUS": 1.01, + "Appnexus": 2.00 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: cannot have multiple bidders that differ only in case style" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json b/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json index 302372b5e5d..e5031d4597d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/interstital-bad-perc.json @@ -49,5 +49,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100\n" + "expectedErrorMessage": "Invalid request: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json index 5aa7fd4dea1..8db15b82a75 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json @@ -39,5 +39,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.source of type string" + "expectedErrorMessage": "Invalid request: cannot unmarshal openrtb_ext.ExtAppPrebid.Source: expects \" or n, but found {" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json b/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json index c6fd52304a7..482af257d62 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/malformed-bid-request.json @@ -2,5 +2,5 @@ "description": "Malformed bid request throws an error", "mockBidRequest": "malformed", "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: invalid character 'm' looking for beginning of value\n" + "expectedErrorMessage": "Invalid request: expect { or n, but found m" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json b/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json similarity index 61% rename from endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json rename to endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json index d21ca4a94ae..c883979c98d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/no-site-or-app-or-dooh.json @@ -1,5 +1,5 @@ { - "description": "Request does not come with site field nor app field", + "description": "Request does not come with site nor app nor dooh field", "mockBidRequest": { "id": "req-id", "imp": [ @@ -19,5 +19,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.site or request.app must be defined, but not both.\n" + "expectedErrorMessage": "Invalid request: request.site should include at least one of request.site.id or request.site.page.\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json index ab44e3e2428..7ab2631b701 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json @@ -42,5 +42,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type map[string]json.RawMessage\n" + "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: expect { or n, but found " } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json b/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json index 5bc3054c356..255ea59e498 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/site-app-both.json @@ -1,5 +1,5 @@ { - "description": "Bid request comes with both site and app fields, it should only come with one or the other", + "description": "Bid request comes with both site and app fields, it should only come with one", "mockBidRequest": { "id": "req-id", "site": { @@ -23,5 +23,5 @@ ] }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.site or request.app must be defined, but not both.\n" + "expectedErrorMessage": "Invalid request: No more than one of request.site or request.app or request.dooh can be defined\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json b/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json new file mode 100644 index 00000000000..d4760d12a99 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/site-dooh-both.json @@ -0,0 +1,27 @@ +{ + "description": "Bid request comes with both site and dooh fields, it should only come with one", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.mysite.com" + }, + "dooh": {}, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: No more than one of request.site or request.app or request.dooh can be defined\n" +} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json index a26db8a5695..af04627c3a9 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json @@ -46,5 +46,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string\n" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 1" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json index c4646550dd2..b710d589ea5 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json @@ -41,5 +41,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 2" } diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json similarity index 53% rename from endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json rename to endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json index cdec20d22ba..67633ab8e1a 100644 --- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-site-publisher.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json @@ -1,10 +1,24 @@ { - "description": "This is a perfectly valid request except that it comes with a blacklisted site publisher account", + "description": "This demonstrates all extension in case insensitive.", "config": { - "blacklistedAccts": ["bad_acct"] + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] }, "mockBidRequest": { "id": "some-request-id", + "site": { + "page": "prebid.org" + }, "user": { "ext": { "consent": "gdpr-consent-string", @@ -15,15 +29,10 @@ } } }, - "site": { - "id": "cool_site", - "publisher": { - "id": "bad_acct" - } - }, "regs": { "ext": { - "gdpr": 1 + "gdpr": 1, + "us_privacy": "1NYN" } }, "imp": [ @@ -42,7 +51,7 @@ ] }, "ext": { - "appnexus": { + "APPnexus": { "placementId": 12883451 }, "districtm": { @@ -60,7 +69,7 @@ "ext": { "prebid": { "aliases": { - "districtm": "appnexus" + "districtm": "Appnexus" }, "bidadjustmentfactors": { "appnexus": 1.01, @@ -70,6 +79,10 @@ "cache": { "bids": {} }, + "channel": { + "name": "video", + "version": "1.0" + }, "targeting": { "includewinners": false, "pricegranularity": { @@ -85,6 +98,43 @@ } } }, - "expectedReturnCode": 503, - "expectedErrorMessage": "Invalid request: Prebid-server has disabled Account ID: bad_acct, please reach out to the prebid server host.\n" + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "APPnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 8f64fd2a5fd..02dc6160d49 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -2,8 +2,16 @@ "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 1.00}, - {"bidderName": "rubicon", "currency": "USD", "price": 1.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } ] }, "mockBidRequest": { @@ -91,42 +99,42 @@ } }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 1.01 - } - ], - "seat": "appnexus" - }, - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0.98 - } - ], - "seat": "districtm" - }, - { - "bid": [ - { - "id": "rubicon-bid", - "impid": "some-impression-id", - "price": 0.99 - } - ], - "seat": "rubicon" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 0.99 + } + ], + "seat": "rubicon" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json new file mode 100644 index 00000000000..a231af657cd --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive-different-casing.json @@ -0,0 +1,97 @@ +{ + "description": "This demonstrates bid adjustment factors with bidder names that have different casing in imp vs. in bidadjustmentfactors", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "bidadjustmentfactors": { + "APPNEXUS": 1.01 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive.json new file mode 100644 index 00000000000..2fa4f37cc88 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/bid-adj-factors-case-insensitive.json @@ -0,0 +1,119 @@ +{ + "description": "This demonstrates bid adjustment factors with case insensitive bidder names", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "ext": { + "consent": "gdpr-consent-string" + } + }, + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1NYN" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "APPNEXUS": { + "placementId": 12883451 + }, + "districtm": { + "placementId": 105 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "APPNEXUS" + }, + "bidadjustmentfactors": { + "APPNEXUS": 1.01, + "districtm": 0.98 + }, + "cache": { + "bids": {} + }, + "channel": { + "name": "video", + "version": "1.0" + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.01 + } + ], + "seat": "APPNEXUS" + }, + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0.98 + } + ], + "seat": "districtm" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json new file mode 100644 index 00000000000..faa9a7f6646 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/dooh.json @@ -0,0 +1,53 @@ +{ + "description": "Simple DOOH request", + "config": { + "mockBidders": [ + {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "dooh": { + "id": "12345" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 1920, + "h": 1080 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json new file mode 100644 index 00000000000..9b66a59d0a5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple-case-insensitive.json @@ -0,0 +1,61 @@ +{ + "description": "Simple request - bidder case insensitive", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "Appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "Appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json index ba9079d4675..5a7dbd20747 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json @@ -2,7 +2,11 @@ "description": "Simple request", "config": { "mockBidders": [ - {"bidderName": "appnexus", "currency": "USD", "price": 0.00} + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } ] }, "mockBidRequest": { @@ -36,22 +40,22 @@ "ext": {} }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0 - } - ], - "seat": "appnexus" - } - ], - "bidid":"test bid id", - "cur":"USD", - "nbr":0 + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json new file mode 100644 index 00000000000..49576023b04 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json @@ -0,0 +1,39 @@ +{ + "description": "request with impression with stored bid response with case matching bidder name", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "prebid": { + "storedbidresponse": [ + { + "bidder": "appnexus", + "id": "bidResponseId3" + } + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json new file mode 100644 index 00000000000..bcebe81472d --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json @@ -0,0 +1,39 @@ +{ + "description": "request with impression with stored bid response with case not matching bidder name", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "prebid": { + "storedbidresponse": [ + { + "bidder": "APPNEXUS", + "id": "bidResponseId3" + } + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json new file mode 100644 index 00000000000..d8685307e20 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json @@ -0,0 +1,34 @@ +{ + "description": "request with impression with stored bid response", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidderABC": {}, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderABC", "id": "bidResponseId1"} + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json index e72cce49355..692a34c4f41 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json @@ -17,12 +17,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "telaria": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json index 5906eb9149c..09b4bc57746 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json @@ -17,12 +17,12 @@ ] }, "ext": { - "appnexus": { + "telaria": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } @@ -38,12 +38,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "amx": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json index 6122e4df066..5387a9d61ae 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json @@ -33,12 +33,12 @@ ] }, "ext": { - "appnexus": { + "amx": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index afe883979ff..a0ac836e30e 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -18,27 +18,28 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -76,7 +77,7 @@ type testCase struct { ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` // "/openrtb2/amp" endpoint JSON test info - storedRequest map[string]json.RawMessage `json:"mockAmpStoredRequest"` + StoredRequest map[string]json.RawMessage `json:"mockAmpStoredRequest"` StoredResponse map[string]json.RawMessage `json:"mockAmpStoredResponse"` ExpectedAmpResponse json.RawMessage `json:"expectedAmpResponse"` } @@ -84,7 +85,6 @@ type testCase struct { type testConfigValues struct { AccountRequired bool `json:"accountRequired"` AliasJSON string `json:"aliases"` - BlacklistedAccounts []string `json:"blacklistedAccts"` BlacklistedApps []string `json:"blacklistedApps"` DisabledAdapters []string `json:"disabledAdapters"` CurrencyRates map[string]map[string]float64 `json:"currencyRates"` @@ -926,13 +926,12 @@ func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request s.data.DataAsOfRaw = "2018-09-12" // Marshal the response and http write it - currencyServerJsonResponse, err := json.Marshal(&s.data) + currencyServerJsonResponse, err := jsonutil.Marshal(&s.data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(currencyServerJsonResponse) - return } // mockBidderHandler carries mock bidder server information that will be read from JSON test files @@ -951,13 +950,13 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { // Unmarshal exit if error var openrtb2Request openrtb2.BidRequest - if err := json.Unmarshal(buf.Bytes(), &openrtb2Request); err != nil { + if err := jsonutil.UnmarshalValid(buf.Bytes(), &openrtb2Request); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } var openrtb2ImpExt map[string]json.RawMessage - if err := json.Unmarshal(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil { + if err := jsonutil.UnmarshalValid(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -988,13 +987,12 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { } // Marshal the response and http write it - serverJsonResponse, err := json.Marshal(&serverResponseObject) + serverJsonResponse, err := jsonutil.Marshal(&serverResponseObject) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(serverJsonResponse) - return } // mockAdapter is a mock impression-splitting adapter @@ -1019,7 +1017,7 @@ func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *ada for _, imp := range request.Imp { requestCopy.Imp = []openrtb2.Imp{imp} - requestJSON, err := json.Marshal(request) + requestJSON, err := jsonutil.Marshal(request) if err != nil { errors = append(errors, err) continue @@ -1052,7 +1050,7 @@ func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapter } var publisherResponse openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &publisherResponse); err != nil { + if err := jsonutil.UnmarshalValid(responseData.Body, &publisherResponse); err != nil { return nil, []error{err} } @@ -1116,7 +1114,7 @@ func parseTestData(fileData []byte, testFile string) (testCase, error) { jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config") if err == nil { - if err = json.Unmarshal(jsonTestConfig, parsedTestData.Config); err != nil { + if err = jsonutil.UnmarshalValid(jsonTestConfig, parsedTestData.Config); err != nil { return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err) } } @@ -1154,18 +1152,6 @@ func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool { return blacklistedAppMap } -func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { - var blacklistedAccountMap map[string]bool - - if len(tc.BlacklistedAccounts) > 0 { - blacklistedAccountMap = make(map[string]bool, len(tc.BlacklistedAccounts)) - for _, account := range tc.BlacklistedAccounts { - blacklistedAccountMap[account] = true - } - } - return blacklistedAccountMap -} - // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call type exchangeTestWrapper struct { ex exchange.Exchange @@ -1218,6 +1204,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid mockFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) testExchange = &exchangeTestWrapper{ @@ -1246,7 +1233,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han bidderInfos := getBidderInfos(test.Config.DisabledAdapters, openrtb_ext.CoreBidderNames()) bidderMap := exchange.GetActiveBidders(bidderInfos) - disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) met := &metricsConfig.NilMetricsEngine{} mockFetcher := empty_fetcher.EmptyFetcher{} @@ -1265,8 +1252,8 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) var storedRequestFetcher stored_requests.Fetcher - if len(test.storedRequest) > 0 { - storedRequestFetcher = &mockAmpStoredReqFetcher{test.storedRequest} + if len(test.StoredRequest) > 0 { + storedRequestFetcher = &mockAmpStoredReqFetcher{test.StoredRequest} } else { storedRequestFetcher = &mockStoredReqFetcher{} } @@ -1278,10 +1265,10 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han storedResponseFetcher = empty_fetcher.EmptyFetcher{} } - var accountFetcher stored_requests.AccountFetcher - accountFetcher = &mockAccountFetcher{ + accountFetcher := &mockAccountFetcher{ data: map[string]json.RawMessage{ "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), }, } @@ -1290,7 +1277,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han planBuilder = hooks.EmptyPlanBuilder{} } - var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder) (httprouter.Handle, error) + var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) switch test.endpointType { case AMP_ENDPOINT: @@ -1307,12 +1294,13 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han accountFetcher, cfg, met, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), disabledBidders, []byte(test.Config.AliasJSON), bidderMap, storedResponseFetcher, planBuilder, + nil, ) return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err @@ -1591,7 +1579,7 @@ var entryPointHookUpdate = hooks.HookWrapper[hookstage.Entrypoint]{ ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { - body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":50}`)) + body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":600}`)) if err == nil { payload.Body = body } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 9401f4da1f9..05802bbd506 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -2,7 +2,6 @@ package openrtb2 import ( "context" - "encoding/json" "errors" "fmt" "io" @@ -18,26 +17,28 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/privacy" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/iputil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" ) var defaultRequestTimeout int64 = 5000 @@ -51,11 +52,12 @@ func NewVideoEndpoint( accounts stored_requests.AccountFetcher, cfg *config.Configuration, met metrics.MetricsEngine, - pbsAnalytics analytics.PBSAnalyticsModule, + analyticsRunner analytics.Runner, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName, cache prebid_cache_client.Client, + tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { @@ -80,7 +82,7 @@ func NewVideoEndpoint( accounts, cfg, met, - pbsAnalytics, + analyticsRunner, disabledBidders, defRequest, defReqJSON, @@ -89,7 +91,9 @@ func NewVideoEndpoint( videoEndpointRegexp, ipValidator, empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}}).VideoAuctionEndpoint), nil + hooks.EmptyPlanBuilder{}, + tmaxAdjustments, + openrtb_ext.NormalizeBidderName}).VideoAuctionEndpoint), nil } /* @@ -146,6 +150,8 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride + activityControl := privacy.ActivityControl{} + defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) @@ -155,7 +161,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } deps.metricsEngine.RecordRequest(labels) deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) - deps.analytics.LogVideoObject(&vo) + deps.analytics.LogVideoObject(&vo, activityControl) }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) @@ -173,7 +179,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re resolvedRequest := requestJson if debugLog.DebugEnabledOrOverridden { debugLog.Data.Request = string(requestJson) - if headerBytes, err := json.Marshal(r.Header); err == nil { + if headerBytes, err := jsonutil.Marshal(r.Header); err == nil { debugLog.Data.Headers = string(headerBytes) } else { debugLog.Data.Headers = fmt.Sprintf("Unable to marshal headers data: %s", err.Error()) @@ -213,7 +219,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re var bidReq = &openrtb2.BidRequest{} if deps.defaultRequest { - if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil { + if err := jsonutil.UnmarshalValid(deps.defReqJSON, bidReq); err != nil { err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err) handleError(&labels, w, []error{err}, &vo, &debugLog) return @@ -241,7 +247,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re for _, podEr := range podErrors { resPodErr = append(resPodErr, strings.Join(podEr.ErrMsgs, ", ")) } - err := errors.New(fmt.Sprintf("all pods are incorrect: %s", strings.Join(resPodErr, "; "))) + err := fmt.Errorf("all pods are incorrect: %s", strings.Join(resPodErr, "; ")) errL = append(errL, err) handleError(&labels, w, errL, &vo, &debugLog) return @@ -275,7 +281,11 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re defer cancel() } - usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) + // Read Usersyncs/Cookie + decoder := usersync.Base64Decoder{} + usersyncs := usersync.ReadCookie(r, decoder, &deps.cfg.HostCookie) + usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie) + if bidReqWrapper.App != nil { labels.Source = metrics.DemandApp labels.PubID = getAccountID(bidReqWrapper.App.Publisher) @@ -296,6 +306,8 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } + activityControl = privacy.NewActivityControl(&account.Privacy) + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ BidRequestWrapper: bidReqWrapper, @@ -307,9 +319,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re GlobalPrivacyControlHeader: secGPC, PubID: labels.PubID, HookExecutor: hookexecution.EmptyHookExecutor{}, + TmaxAdjustments: deps.tmaxAdjustments, + Activities: activityControl, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog) + defer func() { + if !auctionRequest.BidderResponseStartTime.IsZero() { + deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime)) + } + }() vo.RequestWrapper = bidReqWrapper var response *openrtb2.BidResponse if auctionResponse != nil { @@ -355,21 +374,15 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re vo.VideoResponse = bidResp - resp, err := json.Marshal(bidResp) - //resp, err := json.Marshal(response) + resp, err := jsonutil.Marshal(bidResp) if err != nil { errL := []error{err} handleError(&labels, w, errL, &vo, &debugLog) return } - if len(vo.Errors) == 0 { - recordResponsePreparationMetrics(auctionRequest.MakeBidsTimeInfo, deps.metricsEngine) - } - w.Header().Set("Content-Type", "application/json") w.Write(resp) - } func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) *openrtb_ext.BidRequestVideo { @@ -391,7 +404,7 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo var status int = http.StatusInternalServerError for _, er := range errL { erVal := errortypes.ReadCode(er) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { + if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable labels.RequestStatus = metrics.RequestStatusBlacklisted break @@ -492,7 +505,7 @@ func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []err return impr, err } - if err := json.Unmarshal(imp[storedImpId], &impr); err != nil { + if err := jsonutil.UnmarshalValid(imp[storedImpId], &impr); err != nil { return impr, []error{err} } return impr, nil @@ -521,7 +534,7 @@ func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) anyBidsReturned = true var tempRespBidExt openrtb_ext.ExtBid - if err := json.Unmarshal(bid.Ext, &tempRespBidExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, &tempRespBidExt); err != nil { return nil, err } if tempRespBidExt.Prebid.Targeting[formatTargetingKey(openrtb_ext.HbVastCacheKey, seatBid.Seat)] == "" { @@ -543,7 +556,7 @@ func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) if adPod == nil { adPod = &openrtb_ext.AdPod{ PodId: podId, - Targeting: make([]openrtb_ext.VideoTargeting, 0, 0), + Targeting: make([]openrtb_ext.VideoTargeting, 0), } adPods = append(adPods, adPod) } @@ -608,22 +621,15 @@ func getVideoStoredRequestId(request []byte) (string, error) { func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb2.BidRequest) error { if videoRequest.Site != nil { bidRequest.Site = videoRequest.Site - if &videoRequest.Content != nil { - bidRequest.Site.Content = &videoRequest.Content - } + bidRequest.Site.Content = &videoRequest.Content } if videoRequest.App != nil { bidRequest.App = videoRequest.App - if &videoRequest.Content != nil { - bidRequest.App.Content = &videoRequest.Content - } - } - - if &videoRequest.Device != nil { - bidRequest.Device = &videoRequest.Device + bidRequest.App.Content = &videoRequest.Content } + bidRequest.Device = &videoRequest.Device bidRequest.User = videoRequest.User if len(videoRequest.BCat) != 0 { @@ -697,13 +703,13 @@ func createBidExtension(videoRequest *openrtb_ext.BidRequestVideo) ([]byte, erro } extReq := openrtb_ext.ExtRequest{Prebid: prebid} - return json.Marshal(extReq) + return jsonutil.Marshal(extReq) } func (deps *endpointDeps) parseVideoRequest(request []byte, headers http.Header) (req *openrtb_ext.BidRequestVideo, errs []error, podErrors []PodError) { req = &openrtb_ext.BidRequestVideo{} - if err := json.Unmarshal(request, &req); err != nil { + if err := jsonutil.UnmarshalValid(request, &req); err != nil { errs = []error{err} return } @@ -756,7 +762,7 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo) err := errors.New("request missing required field: PodConfig.Pods") errL = append(errL, err) } - podErrors := make([]PodError, 0, 0) + podErrors := make([]PodError, 0) podIdsSet := make(map[int]bool) for ind, pod := range req.PodConfig.Pods { podErr := PodError{} diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 496a2ac8cff..70a37aab5df 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -11,18 +11,20 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" @@ -46,7 +48,7 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -79,7 +81,7 @@ func TestVideoEndpointImpressionsDuration(t *testing.T) { } var extData openrtb_ext.ExtRequest - json.Unmarshal(ex.lastRequest.Ext, &extData) + jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData) assert.NotNil(t, extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") assert.True(t, *extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") @@ -134,7 +136,7 @@ func TestCreateBidExtension(t *testing.T) { resExt := &openrtb_ext.ExtRequest{} - if err := json.Unmarshal(res, &resExt); err != nil { + if err := jsonutil.UnmarshalValid(res, &resExt); err != nil { assert.Fail(t, "Unable to unmarshal bid extension") } assert.Equal(t, resExt.Prebid.Targeting.DurationRangeSec, durationRange, "Duration range seconds is incorrect") @@ -177,7 +179,7 @@ func TestVideoEndpointDebugQueryTrue(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -215,7 +217,7 @@ func TestVideoEndpointDebugQueryFalse(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -271,7 +273,7 @@ func TestVideoEndpointDebugNoAdPods(t *testing.T) { respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -292,7 +294,7 @@ func TestVideoEndpointNoPods(t *testing.T) { deps := mockDeps(t, ex) deps.VideoAuctionEndpoint(recorder, req, nil) - errorMessage := string(recorder.Body.Bytes()) + errorMessage := recorder.Body.String() assert.Equal(t, recorder.Code, 500, "Should catch error in request") assert.Equal(t, "Critical error while running the video endpoint: request missing required field: PodConfig.DurationRangeSec request missing required field: PodConfig.Pods", errorMessage, "Incorrect request validation message") @@ -739,7 +741,7 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { func TestVideoBuildVideoResponseNoBids(t *testing.T) { openRtbBidResp := openrtb2.BidResponse{} - podErrors := make([]PodError, 0, 0) + podErrors := make([]PodError, 0) openRtbBidResp.SeatBid = make([]openrtb2.SeatBid, 0) bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors) assert.NoError(t, err, "Error should be nil") @@ -808,7 +810,7 @@ func TestHandleError(t *testing.T) { { description: "Blocked account - return 503 with blocked metrics status", giveErrors: []error{ - &errortypes.BlacklistedAcct{}, + &errortypes.AccountDisabled{}, }, wantCode: 503, wantMetricsStatus: metrics.RequestStatusBlacklisted, @@ -1090,7 +1092,7 @@ func TestCCPA(t *testing.T) { t.Fatalf("%s: The request never made it into the exchange.", test.description) } extRegs := &openrtb_ext.ExtRegs{} - if err := json.Unmarshal(ex.lastRequest.Regs.Ext, extRegs); err != nil { + if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil { t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err) } if test.expectConsentString { @@ -1102,7 +1104,7 @@ func TestCCPA(t *testing.T) { // Validate HTTP Response responseBytes := httpResponseRecorder.Body.Bytes() response := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(responseBytes, response); err != nil { + if err := jsonutil.UnmarshalValid(responseBytes, response); err != nil { t.Fatalf("%s: Unable to unmarshal response.", test.description) } assert.Len(t, ex.lastRequest.Imp, 11, test.description+":imps") @@ -1124,12 +1126,12 @@ func TestVideoEndpointAppendBidderNames(t *testing.T) { } var extData openrtb_ext.ExtRequest - json.Unmarshal(ex.lastRequest.Ext, &extData) + jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData) assert.True(t, extData.Prebid.Targeting.AppendBidderNames, "Request ext incorrect: AppendBidderNames should be true ") respBytes := recorder.Body.Bytes() resp := &openrtb_ext.BidResponseVideo{} - if err := json.Unmarshal(respBytes, resp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil { t.Fatalf("Unable to unmarshal response.") } @@ -1225,6 +1227,8 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } return deps, metrics, mockModule } @@ -1234,11 +1238,11 @@ type mockAnalyticsModule struct { videoObjects []*analytics.VideoObject } -func (m *mockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject) { +func (m *mockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) { m.auctionObjects = append(m.auctionObjects, ao) } -func (m *mockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject) { +func (m *mockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) { m.videoObjects = append(m.videoObjects, vo) } @@ -1246,9 +1250,10 @@ func (m *mockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObjec func (m *mockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) {} -func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject) {} +func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {} -func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent) {} +func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) { +} func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { return &endpointDeps{ @@ -1260,7 +1265,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockAccountFetcher{data: mockVideoAccountData}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1270,6 +1275,8 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } } @@ -1283,7 +1290,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1293,6 +1300,8 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } return deps @@ -1308,7 +1317,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), + analyticsBuild.New(&config.Analytics{}), map[string]string{}, false, []byte{}, @@ -1318,6 +1327,8 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { hardcodedResponseIPValidator{response: true}, empty_fetcher.EmptyFetcher{}, hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, } return edep diff --git a/endpoints/setuid.go b/endpoints/setuid.go index a4d04749eae..0f4d36d35ac 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -3,21 +3,28 @@ package endpoints import ( "context" "errors" + "fmt" "net/http" "net/url" "strconv" "strings" - "time" "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" + gpplib "github.com/prebid/go-gpp" + gppConstants "github.com/prebid/go-gpp/constants" + accountService "github.com/prebid/prebid-server/v2/account" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + gppPrivacy "github.com/prebid/prebid-server/v2/privacy/gpp" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/httputil" + stringutil "github.com/prebid/prebid-server/v2/util/stringutil" ) const ( @@ -28,15 +35,11 @@ const ( chromeiOSStrLen = len(chromeiOSStr) ) -func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, pbsanalytics analytics.PBSAnalyticsModule, accountsFetcher stored_requests.AccountFetcher, metricsEngine metrics.MetricsEngine) httprouter.Handle { - cookieTTL := time.Duration(cfg.HostCookie.TTL) * 24 * time.Hour +const uidCookieName = "uids" - // convert map of syncers by bidder to map of syncers by key - // - its safe to assume that if multiple bidders map to the same key, the syncers are interchangeable. - syncersByKey := make(map[string]usersync.Syncer, len(syncersByBidder)) - for _, v := range syncersByBidder { - syncersByKey[v.Key()] = v - } +func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPermsBuilder gdpr.PermissionsBuilder, tcf2CfgBuilder gdpr.TCF2ConfigBuilder, analyticsRunner analytics.Runner, accountsFetcher stored_requests.AccountFetcher, metricsEngine metrics.MetricsEngine) httprouter.Handle { + encoder := usersync.Base64Encoder{} + decoder := usersync.Base64Decoder{} return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { so := analytics.SetUIDObject{ @@ -44,36 +47,27 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use Errors: make([]error, 0), } - defer pbsanalytics.LogSetUIDObject(&so) + defer analyticsRunner.LogSetUIDObject(&so) - pc := usersync.ParseCookieFromRequest(r, &cfg.HostCookie) - if !pc.AllowSyncs() { - w.WriteHeader(http.StatusUnauthorized) - metricsEngine.RecordSetUid(metrics.SetUidOptOut) - so.Status = http.StatusUnauthorized + cookie := usersync.ReadCookie(r, decoder, &cfg.HostCookie) + if !cookie.AllowSyncs() { + handleBadStatus(w, http.StatusUnauthorized, metrics.SetUidOptOut, nil, metricsEngine, &so) return } + usersync.SyncHostCookie(r, cookie, &cfg.HostCookie) query := r.URL.Query() - syncer, err := getSyncer(query, syncersByKey) + syncer, bidderName, err := getSyncer(query, syncersByBidder) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - metricsEngine.RecordSetUid(metrics.SetUidSyncerUnknown) - so.Errors = []error{err} - so.Status = http.StatusBadRequest + handleBadStatus(w, http.StatusBadRequest, metrics.SetUidSyncerUnknown, err, metricsEngine, &so) return } so.Bidder = syncer.Key() responseFormat, err := getResponseFormat(query, syncer) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - metricsEngine.RecordSetUid(metrics.SetUidBadRequest) - so.Errors = []error{err} - so.Status = http.StatusBadRequest + handleBadStatus(w, http.StatusBadRequest, metrics.SetUidBadRequest, err, metricsEngine, &so) return } @@ -83,37 +77,68 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use } account, fetchErrs := accountService.GetAccount(context.Background(), cfg, accountsFetcher, accountID, metricsEngine) if len(fetchErrs) > 0 { - w.WriteHeader(http.StatusBadRequest) + var metricValue metrics.SetUidStatus err := combineErrors(fetchErrs) - w.Write([]byte(err.Error())) switch err { case errCookieSyncAccountBlocked: - metricsEngine.RecordSetUid(metrics.SetUidAccountBlocked) + metricValue = metrics.SetUidAccountBlocked case errCookieSyncAccountConfigMalformed: - metricsEngine.RecordSetUid(metrics.SetUidAccountConfigMalformed) + metricValue = metrics.SetUidAccountConfigMalformed case errCookieSyncAccountInvalid: - metricsEngine.RecordSetUid(metrics.SetUidAccountInvalid) + metricValue = metrics.SetUidAccountInvalid default: - metricsEngine.RecordSetUid(metrics.SetUidBadRequest) + metricValue = metrics.SetUidBadRequest } + handleBadStatus(w, http.StatusBadRequest, metricValue, err, metricsEngine, &so) + return + } + + activityControl := privacy.NewActivityControl(&account.Privacy) + + gppSID, err := stringutil.StrToInt8Slice(query.Get("gpp_sid")) + if err != nil { + err := fmt.Errorf("invalid gpp_sid encoding, must be a csv list of integers") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + metricsEngine.RecordSetUid(metrics.SetUidBadRequest) so.Errors = []error{err} so.Status = http.StatusBadRequest return } + policies := privacy.Policies{ + GPPSID: gppSID, + } + + userSyncActivityAllowed := activityControl.Allow(privacy.ActivitySyncUser, + privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName}, + privacy.NewRequestFromPolicies(policies)) + + if !userSyncActivityAllowed { + w.WriteHeader(http.StatusUnavailableForLegalReasons) + return + } + + gdprRequestInfo, err := extractGDPRInfo(query) + if err != nil { + // Only exit if non-warning + if !errortypes.IsWarning(err) { + handleBadStatus(w, http.StatusBadRequest, metrics.SetUidBadRequest, err, metricsEngine, &so) + return + } + } + tcf2Cfg := tcf2CfgBuilder(cfg.GDPR.TCF2, account.GDPR) - if shouldReturn, status, body := preventSyncsGDPR(query.Get("gdpr"), query.Get("gdpr_consent"), gdprPermsBuilder, tcf2Cfg); shouldReturn { - w.WriteHeader(status) - w.Write([]byte(body)) + if shouldReturn, status, body := preventSyncsGDPR(gdprRequestInfo, gdprPermsBuilder, tcf2Cfg); shouldReturn { + var metricValue metrics.SetUidStatus switch status { case http.StatusBadRequest: - metricsEngine.RecordSetUid(metrics.SetUidBadRequest) + metricValue = metrics.SetUidBadRequest case http.StatusUnavailableForLegalReasons: - metricsEngine.RecordSetUid(metrics.SetUidGDPRHostCookieBlocked) + metricValue = metrics.SetUidGDPRHostCookieBlocked } - so.Errors = []error{errors.New(body)} - so.Status = status + handleBadStatus(w, status, metricValue, errors.New(body), metricsEngine, &so) return } @@ -121,18 +146,36 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use so.UID = uid if uid == "" { - pc.Unsync(syncer.Key()) + cookie.Unsync(syncer.Key()) metricsEngine.RecordSetUid(metrics.SetUidOK) metricsEngine.RecordSyncerSet(syncer.Key(), metrics.SyncerSetUidCleared) so.Success = true - } else if err = pc.TrySync(syncer.Key(), uid); err == nil { + } else if err = cookie.Sync(syncer.Key(), uid); err == nil { metricsEngine.RecordSetUid(metrics.SetUidOK) metricsEngine.RecordSyncerSet(syncer.Key(), metrics.SyncerSetUidOK) so.Success = true } setSiteCookie := siteCookieCheck(r.UserAgent()) - pc.SetCookieOnResponse(w, setSiteCookie, &cfg.HostCookie, cookieTTL) + + // Priority Ejector Set Up + priorityEjector := &usersync.PriorityBidderEjector{PriorityGroups: cfg.UserSync.PriorityGroups, TieEjector: &usersync.OldestEjector{}, SyncersByBidder: syncersByBidder} + priorityEjector.IsSyncerPriority = isSyncerPriority(bidderName, cfg.UserSync.PriorityGroups) + + // Write Cookie + encodedCookie, err := cookie.PrepareCookieForWrite(&cfg.HostCookie, encoder, priorityEjector) + if err != nil { + if err.Error() == errSyncerIsNotPriority.Error() { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Warning: " + err.Error() + ", cookie not updated")) + so.Status = http.StatusOK + return + } else { + handleBadStatus(w, http.StatusBadRequest, metrics.SetUidBadRequest, err, metricsEngine, &so) + return + } + } + usersync.WriteCookie(w, encodedCookie, &cfg.HostCookie, setSiteCookie) switch responseFormat { case "i": @@ -148,19 +191,167 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use }) } -func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, error) { - key := query.Get("bidder") +// extractGDPRInfo looks for the GDPR consent string and GDPR signal in the GPP query params +// first and the 'gdpr' and 'gdpr_consent' query params second. If found in both, throws a +// warning. Can also throw a parsing or validation error +func extractGDPRInfo(query url.Values) (reqInfo gdpr.RequestInfo, err error) { + reqInfo, err = parseGDPRFromGPP(query) + if err != nil { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err + } + + legacySignal, legacyConsent, err := parseLegacyGDPRFields(query, reqInfo.GDPRSignal, reqInfo.Consent) + isWarning := errortypes.IsWarning(err) + + if err != nil && !isWarning { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err + } + + // If no GDPR data in the GPP fields, use legacy instead + if reqInfo.Consent == "" && reqInfo.GDPRSignal == gdpr.SignalAmbiguous { + reqInfo.GDPRSignal = legacySignal + reqInfo.Consent = legacyConsent + } + + if reqInfo.Consent == "" && reqInfo.GDPRSignal == gdpr.SignalYes { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, errors.New("GDPR consent is required when gdpr signal equals 1") + } + + return reqInfo, err +} + +// parseGDPRFromGPP parses and validates the "gpp_sid" and "gpp" query fields. +func parseGDPRFromGPP(query url.Values) (gdpr.RequestInfo, error) { + var gdprSignal gdpr.Signal = gdpr.SignalAmbiguous + var gdprConsent string = "" + var err error + + gdprSignal, err = parseSignalFromGppSidStr(query.Get("gpp_sid")) + if err != nil { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err + } - if key == "" { - return nil, errors.New(`"bidder" query param is required`) + gdprConsent, err = parseConsentFromGppStr(query.Get("gpp")) + if err != nil { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err } - syncer, syncerExists := syncersByKey[key] + return gdpr.RequestInfo{ + Consent: gdprConsent, + GDPRSignal: gdprSignal, + }, nil +} + +// parseLegacyGDPRFields parses and validates the "gdpr" and "gdpr_consent" query fields which +// are considered deprecated in favor of the "gpp" and "gpp_sid". The parsed and validated GDPR +// values contained in "gpp" and "gpp_sid" are passed in the parameters gppGDPRSignal and +// gppGDPRConsent. If the GPP parameters come with non-default values, this function discards +// "gdpr" and "gdpr_consent" and returns a warning. +func parseLegacyGDPRFields(query url.Values, gppGDPRSignal gdpr.Signal, gppGDPRConsent string) (gdpr.Signal, string, error) { + var gdprSignal gdpr.Signal = gdpr.SignalAmbiguous + var gdprConsent string + var warning error + + if gdprQuerySignal := query.Get("gdpr"); len(gdprQuerySignal) > 0 { + if gppGDPRSignal == gdpr.SignalAmbiguous { + switch gdprQuerySignal { + case "0": + fallthrough + case "1": + if zeroOrOne, err := strconv.Atoi(gdprQuerySignal); err == nil { + gdprSignal = gdpr.Signal(zeroOrOne) + } + default: + return gdpr.SignalAmbiguous, "", errors.New("the gdpr query param must be either 0 or 1. You gave " + gdprQuerySignal) + } + } else { + warning = &errortypes.Warning{ + Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", + WarningCode: errortypes.UnknownWarningCode, + } + } + } + + if gdprLegacyConsent := query.Get("gdpr_consent"); len(gdprLegacyConsent) > 0 { + if len(gppGDPRConsent) > 0 { + warning = &errortypes.Warning{ + Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", + WarningCode: errortypes.UnknownWarningCode, + } + } else { + gdprConsent = gdprLegacyConsent + } + } + return gdprSignal, gdprConsent, warning +} + +func parseSignalFromGppSidStr(strSID string) (gdpr.Signal, error) { + gdprSignal := gdpr.SignalAmbiguous + + if len(strSID) > 0 { + gppSID, err := stringutil.StrToInt8Slice(strSID) + if err != nil { + return gdpr.SignalAmbiguous, fmt.Errorf("Error parsing gpp_sid %s", err.Error()) + } + + if len(gppSID) > 0 { + gdprSignal = gdpr.SignalNo + if gppPrivacy.IsSIDInList(gppSID, gppConstants.SectionTCFEU2) { + gdprSignal = gdpr.SignalYes + } + } + } + + return gdprSignal, nil +} + +func parseConsentFromGppStr(gppQueryValue string) (string, error) { + var gdprConsent string + + if len(gppQueryValue) > 0 { + gpp, err := gpplib.Parse(gppQueryValue) + if err != nil { + return "", err + } + + if i := gppPrivacy.IndexOfSID(gpp, gppConstants.SectionTCFEU2); i >= 0 { + gdprConsent = gpp.Sections[i].GetValue() + } + } + + return gdprConsent, nil +} + +func getSyncer(query url.Values, syncersByBidder map[string]usersync.Syncer) (usersync.Syncer, string, error) { + bidder := query.Get("bidder") + + if bidder == "" { + return nil, "", errors.New(`"bidder" query param is required`) + } + + // case insensitive comparison + bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder) + if !bidderFound { + return nil, "", errors.New("The bidder name provided is not supported by Prebid Server") + } + + syncer, syncerExists := syncersByBidder[bidderNormalized.String()] if !syncerExists { - return nil, errors.New("The bidder name provided is not supported by Prebid Server") + return nil, "", errors.New("The bidder name provided is not supported by Prebid Server") } - return syncer, nil + return syncer, bidder, nil +} + +func isSyncerPriority(bidderNameFromSyncerQuery string, priorityGroups [][]string) bool { + for _, group := range priorityGroups { + for _, bidder := range group { + if strings.EqualFold(bidderNameFromSyncerQuery, bidder) { + return true + } + } + } + return false } // getResponseFormat reads the format query parameter or falls back to the syncer's default. @@ -171,7 +362,7 @@ func getResponseFormat(query url.Values, syncer usersync.Syncer) (string, error) formatEmpty := len(format) == 0 || format[0] == "" if !formatProvided || formatEmpty { - switch syncer.DefaultSyncType() { + switch syncer.DefaultResponseFormat() { case usersync.SyncTypeIFrame: return "b", nil case usersync.SyncTypeRedirect: @@ -216,26 +407,7 @@ func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool { return result } -func preventSyncsGDPR(gdprEnabled string, gdprConsent string, permsBuilder gdpr.PermissionsBuilder, tcf2Cfg gdpr.TCF2ConfigReader) (shouldReturn bool, status int, body string) { - if gdprEnabled != "" && gdprEnabled != "0" && gdprEnabled != "1" { - return true, http.StatusBadRequest, "the gdpr query param must be either 0 or 1. You gave " + gdprEnabled - } - - if gdprEnabled == "1" && gdprConsent == "" { - return true, http.StatusBadRequest, "gdpr_consent is required when gdpr=1" - } - - gdprSignal := gdpr.SignalAmbiguous - - if i, err := strconv.Atoi(gdprEnabled); err == nil { - gdprSignal = gdpr.Signal(i) - } - - gdprRequestInfo := gdpr.RequestInfo{ - Consent: gdprConsent, - GDPRSignal: gdprSignal, - } - +func preventSyncsGDPR(gdprRequestInfo gdpr.RequestInfo, permsBuilder gdpr.PermissionsBuilder, tcf2Cfg gdpr.TCF2ConfigReader) (shouldReturn bool, status int, body string) { perms := permsBuilder(tcf2Cfg, gdprRequestInfo) allowed, err := perms.HostCookiesAllowed(context.Background()) @@ -257,3 +429,14 @@ func preventSyncsGDPR(gdprEnabled string, gdprConsent string, permsBuilder gdpr. return true, http.StatusUnavailableForLegalReasons, "The gdpr_consent string prevents cookies from being saved" } + +func handleBadStatus(w http.ResponseWriter, status int, metricValue metrics.SetUidStatus, err error, me metrics.MetricsEngine, so *analytics.SetUIDObject) { + w.WriteHeader(status) + me.RecordSetUid(metricValue) + so.Status = status + + if err != nil { + so.Errors = []error{err} + w.Write([]byte(err.Error())) + } +} diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 609d85395fd..ded528abeb3 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -8,20 +8,22 @@ import ( "net/http/httptest" "net/url" "regexp" + "strings" "testing" "time" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/analytics" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/usersync" "github.com/stretchr/testify/assert" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - metricsConf "github.com/prebid/prebid-server/metrics/config" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" ) func TestSetUIDEndpoint(t *testing.T) { @@ -32,6 +34,7 @@ func TestSetUIDEndpoint(t *testing.T) { gdprAllowsHostCookies bool gdprReturnsError bool gdprMalformed bool + formatOverride string expectedSyncs map[string]string expectedBody string expectedStatusCode int @@ -49,7 +52,17 @@ func TestSetUIDEndpoint(t *testing.T) { description: "Set uid for valid bidder", }, { - uri: "/setuid?bidder=adnxs&uid=123", + uri: "/setuid?bidder=PUBMATIC&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder case insensitive", + }, + { + uri: "/setuid?bidder=appnexus&uid=123", syncersBidderNameToKey: map[string]string{"appnexus": "adnxs"}, existingSyncs: nil, gdprAllowsHostCookies: true, @@ -157,7 +170,7 @@ func TestSetUIDEndpoint(t *testing.T) { expectedSyncs: nil, gdprAllowsHostCookies: true, expectedStatusCode: http.StatusBadRequest, - expectedBody: "gdpr_consent is required when gdpr=1", + expectedBody: "GDPR consent is required when gdpr signal equals 1", description: "Return an error if GDPR is set to 1 but GDPR consent string is missing", }, { @@ -194,8 +207,39 @@ func TestSetUIDEndpoint(t *testing.T) { description: "Should set uid for a bidder that is allowed by the GDPR consent string", }, { - uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + - "malformed", + uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + existingSyncs: nil, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Sets uid for a bidder allowed by GDPR consent string in the GPP query field", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" + + "&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + existingSyncs: nil, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "GPP value will be used over the one found in the deprecated GDPR consent field for iframe format", + }, + { + uri: "/setuid?f=i&bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" + + "&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + existingSyncs: nil, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "image/png", "Content-Length": "86"}, + description: "GPP value will be used over the one found in the deprecated GDPR consent field for redirect format", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=malformed", syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, gdprAllowsHostCookies: true, gdprMalformed: true, @@ -254,14 +298,74 @@ func TestSetUIDEndpoint(t *testing.T) { expectedBody: "account is disabled, please reach out to the prebid server host", description: "Set uid for valid bidder with valid disabled account provided", }, + { + uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_enabled", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder with valid account provided with user sync allowed activity", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_disabled", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusUnavailableForLegalReasons, + description: "Set uid for valid bidder with valid account provided with user sync disallowed activity", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_invalid_activities", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder with valid account provided with invalid user sync activity", + }, + { + description: "gppsid-valid", + uri: "/setuid?bidder=appnexus&uid=123&gpp_sid=100,101", // fake sids to avoid GDPR logic in this test + syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"appnexus": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + }, + { + description: "gppsid-malformed", + uri: "/setuid?bidder=appnexus&uid=123&gpp_sid=malformed", + syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "invalid gpp_sid encoding, must be a csv list of integers", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + formatOverride: "i", + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Length": "86", "Content-Type": "image/png"}, + description: "Format not provided in URL, but formatOverride is defined", + }, } - analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) + analytics := analyticsBuild.New(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} for _, test := range testCases { response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics, - test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false) + test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false, 0, nil, test.formatOverride) assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) if test.expectedSyncs != nil { @@ -288,6 +392,726 @@ func TestSetUIDEndpoint(t *testing.T) { } } +func TestSetUIDPriorityEjection(t *testing.T) { + decoder := usersync.Base64Decoder{} + analytics := analyticsBuild.New(&config.Analytics{}) + syncersByBidder := map[string]string{ + "pubmatic": "pubmatic", + "syncer1": "syncer1", + "syncer2": "syncer2", + "syncer3": "syncer3", + "syncer4": "syncer4", + "mismatchedBidderName": "syncer5", + "syncerToEject": "syncerToEject", + } + + testCases := []struct { + description string + uri string + givenExistingSyncs []string + givenPriorityGroups [][]string + givenMaxCookieSize int + expectedStatusCode int + expectedSyncer string + expectedUID string + expectedNumOfElements int + expectedWarning string + }{ + { + description: "Cookie empty, expect bidder to be synced, no ejection", + uri: "/setuid?bidder=pubmatic&uid=123", + givenPriorityGroups: [][]string{}, + givenMaxCookieSize: 500, + expectedSyncer: "pubmatic", + expectedUID: "123", + expectedNumOfElements: 1, + expectedStatusCode: http.StatusOK, + }, + { + description: "Cookie full, no priority groups, one ejection", + uri: "/setuid?bidder=pubmatic&uid=123", + givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer4"}, + givenPriorityGroups: [][]string{}, + givenMaxCookieSize: 500, + expectedUID: "123", + expectedSyncer: "pubmatic", + expectedNumOfElements: 4, + expectedStatusCode: http.StatusOK, + }, + { + description: "Cookie full, eject lowest priority element", + uri: "/setuid?bidder=pubmatic&uid=123", + givenExistingSyncs: []string{"syncer2", "syncer3", "syncer4", "syncerToEject"}, + givenPriorityGroups: [][]string{{"pubmatic", "syncer2", "syncer3", "syncer4"}, {"syncerToEject"}}, + givenMaxCookieSize: 500, + expectedUID: "123", + expectedSyncer: "pubmatic", + expectedNumOfElements: 4, + expectedStatusCode: http.StatusOK, + }, + { + description: "Cookie full, all elements same priority, one ejection", + uri: "/setuid?bidder=pubmatic&uid=123", + givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer5"}, + givenPriorityGroups: [][]string{{"pubmatic", "syncer1", "syncer2", "syncer3", "mismatchedBidderName"}}, + givenMaxCookieSize: 500, + expectedUID: "123", + expectedSyncer: "pubmatic", + expectedNumOfElements: 4, + expectedStatusCode: http.StatusOK, + }, + { + description: "There are only priority elements left, but the bidder being synced isn't one", + uri: "/setuid?bidder=pubmatic&uid=123", + givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer4"}, + givenPriorityGroups: [][]string{{"syncer1", "syncer2", "syncer3", "syncer4"}}, + givenMaxCookieSize: 500, + expectedStatusCode: http.StatusOK, + expectedWarning: "Warning: syncer key is not a priority, and there are only priority elements left, cookie not updated", + }, + { + description: "Uid that's trying to be synced is bigger than MaxCookieSize", + uri: "/setuid?bidder=pubmatic&uid=123", + givenMaxCookieSize: 1, + expectedStatusCode: http.StatusBadRequest, + }, + } + for _, test := range testCases { + request := httptest.NewRequest("GET", test.uri, nil) + + // Cookie Set Up + cookie := usersync.NewCookie() + for _, key := range test.givenExistingSyncs { + cookie.Sync(key, "111") + } + httpCookie, err := ToHTTPCookie(cookie) + assert.NoError(t, err) + request.AddCookie(httpCookie) + + // Make Request to /setuid + response := doRequest(request, analytics, &metricsConf.NilMetricsEngine{}, syncersByBidder, true, false, false, false, test.givenMaxCookieSize, test.givenPriorityGroups, "") + + if test.expectedWarning != "" { + assert.Equal(t, test.expectedWarning, response.Body.String(), test.description) + } else if test.expectedSyncer != "" { + // Get Cookie From Header + var cookieHeader string + for k, v := range response.Result().Header { + if k == "Set-Cookie" { + cookieHeader = v[0] + } + } + encodedCookieValue := getUIDFromHeader(cookieHeader) + + // Check That Bidder On Request was Synced, it's UID matches, and that the right number of elements are present after ejection + decodedCookie := decoder.Decode(encodedCookieValue) + decodedCookieUIDs := decodedCookie.GetUIDs() + + assert.Equal(t, test.expectedUID, decodedCookieUIDs[test.expectedSyncer], test.description) + assert.Equal(t, test.expectedNumOfElements, len(decodedCookieUIDs), test.description) + + // Specific test case handling where we eject the lowest priority element + if len(test.givenPriorityGroups) == 2 { + syncer := test.givenPriorityGroups[len(test.givenPriorityGroups)-1][0] + _, syncerExists := decodedCookieUIDs[syncer] + assert.False(t, syncerExists, test.description) + } + } + assert.Equal(t, test.expectedStatusCode, response.Result().StatusCode, test.description) + } +} + +func TestParseSignalFromGPPSID(t *testing.T) { + type testOutput struct { + signal gdpr.Signal + err error + } + testCases := []struct { + desc string + strSID string + expected testOutput + }{ + { + desc: "Empty gpp_sid, expect gdpr.SignalAmbiguous", + strSID: "", + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + err: nil, + }, + }, + { + desc: "Malformed gpp_sid, expect gdpr.SignalAmbiguous", + strSID: "malformed", + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + err: errors.New(`Error parsing gpp_sid strconv.ParseInt: parsing "malformed": invalid syntax`), + }, + }, + { + desc: "Valid gpp_sid doesn't come with TCF2, expect gdpr.SignalNo", + strSID: "6", + expected: testOutput{ + signal: gdpr.SignalNo, + err: nil, + }, + }, + { + desc: "Valid gpp_sid comes with TCF2, expect gdpr.SignalYes", + strSID: "2", + expected: testOutput{ + signal: gdpr.SignalYes, + err: nil, + }, + }, + } + for _, tc := range testCases { + outSignal, outErr := parseSignalFromGppSidStr(tc.strSID) + + assert.Equal(t, tc.expected.signal, outSignal, tc.desc) + assert.Equal(t, tc.expected.err, outErr, tc.desc) + } +} + +func TestParseConsentFromGppStr(t *testing.T) { + type testOutput struct { + gdprConsent string + err error + } + testCases := []struct { + desc string + inGppQuery string + expected testOutput + }{ + { + desc: "Empty gpp field, expect empty GDPR consent", + inGppQuery: "", + expected: testOutput{ + gdprConsent: "", + err: nil, + }, + }, + { + desc: "Malformed gpp field value, expect empty GDPR consent and error", + inGppQuery: "malformed", + expected: testOutput{ + gdprConsent: "", + err: errors.New(`error parsing GPP header, base64 decoding: illegal base64 data at input byte 8`), + }, + }, + { + desc: "Valid gpp string comes with TCF2 in its gppConstants.SectionID's, expect non-empty GDPR consent", + inGppQuery: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + expected: testOutput{ + gdprConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + err: nil, + }, + }, + { + desc: "Valid gpp string doesn't come with TCF2 in its gppConstants.SectionID's, expect blank GDPR consent", + inGppQuery: "DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + expected: testOutput{ + gdprConsent: "", + err: nil, + }, + }, + } + for _, tc := range testCases { + outConsent, outErr := parseConsentFromGppStr(tc.inGppQuery) + + assert.Equal(t, tc.expected.gdprConsent, outConsent, tc.desc) + assert.Equal(t, tc.expected.err, outErr, tc.desc) + } +} + +func TestParseGDPRFromGPP(t *testing.T) { + type testOutput struct { + reqInfo gdpr.RequestInfo + err error + } + type aTest struct { + desc string + inUri string + expected testOutput + } + testGroups := []struct { + groupDesc string + testCases []aTest + }{ + { + groupDesc: "No gpp_sid nor gpp", + testCases: []aTest{ + { + desc: "Input URL is mising gpp_sid and gpp, expect signal ambiguous and no error", + inUri: "/setuid?bidder=pubmatic&uid=123", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: nil, + }, + }, + }, + }, + { + groupDesc: "gpp only", + testCases: []aTest{ + { + desc: "gpp is malformed, expect error", + inUri: "/setuid?gpp=malformed", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), + }, + }, + { + desc: "gpp with a valid TCF2 value. Expect valid consent string and no error", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{ + GDPRSignal: gdpr.SignalAmbiguous, + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + }, + err: nil, + }, + }, + { + desc: "gpp does not include TCF2 string. Expect empty consent string and no error", + inUri: "/setuid?gpp=DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{ + GDPRSignal: gdpr.SignalAmbiguous, + Consent: "", + }, + err: nil, + }, + }, + }, + }, + { + groupDesc: "gpp_sid only", + testCases: []aTest{ + { + desc: "gpp_sid is malformed, expect error", + inUri: "/setuid?gpp_sid=malformed", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"), + }, + }, + { + desc: "TCF2 found in gpp_sid list. Given that the consent string will be empty, expect an error", + inUri: "/setuid?gpp_sid=2,6", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalYes}, + err: nil, + }, + }, + { + desc: "TCF2 not found in gpp_sid list. Expect SignalNo and no error", + inUri: "/setuid?gpp_sid=6,8", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo}, + err: nil, + }, + }, + }, + }, + { + groupDesc: "both gpp_sid and gpp", + testCases: []aTest{ + { + desc: "TCF2 found in gpp_sid list and gpp has a valid GDPR string. Expect no error", + inUri: "/setuid?gpp_sid=2,6&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + expected: testOutput{ + reqInfo: gdpr.RequestInfo{ + GDPRSignal: gdpr.SignalYes, + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + }, + err: nil, + }, + }, + }, + }, + } + for _, tgroup := range testGroups { + for _, tc := range tgroup.testCases { + // set test + testURL, err := url.Parse(tc.inUri) + assert.NoError(t, err, "%s - %s", tgroup.groupDesc, tc.desc) + + query := testURL.Query() + + // run + outReqInfo, outErr := parseGDPRFromGPP(query) + + // assertions + assert.Equal(t, tc.expected.reqInfo, outReqInfo, "%s - %s", tgroup.groupDesc, tc.desc) + assert.Equal(t, tc.expected.err, outErr, "%s - %s", tgroup.groupDesc, tc.desc) + } + } +} + +func TestParseLegacyGDPRFields(t *testing.T) { + type testInput struct { + uri string + gppGDPRSignal gdpr.Signal + gppGDPRConsent string + } + type testOutput struct { + signal gdpr.Signal + consent string + err error + } + testCases := []struct { + desc string + in testInput + expected testOutput + }{ + { + desc: `both "gdpr" and "gdpr_consent" missing from URI, expect SignalAmbiguous, blank consent and no error`, + in: testInput{ + uri: "/setuid?bidder=pubmatic&uid=123", + }, + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + consent: "", + err: nil, + }, + }, + { + desc: `invalid "gdpr" value, expect SignalAmbiguous, blank consent and error`, + in: testInput{ + uri: "/setuid?gdpr=2", + gppGDPRSignal: gdpr.SignalAmbiguous, + }, + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + consent: "", + err: errors.New("the gdpr query param must be either 0 or 1. You gave 2"), + }, + }, + { + desc: `valid "gdpr" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`, + in: testInput{ + uri: "/setuid?gdpr=1", + gppGDPRSignal: gdpr.SignalYes, + }, + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + consent: "", + err: &errortypes.Warning{ + Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + { + desc: `valid "gdpr_consent" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`, + in: testInput{ + uri: "/setuid?gdpr_consent=someConsent", + gppGDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + }, + expected: testOutput{ + signal: gdpr.SignalAmbiguous, + consent: "", + err: &errortypes.Warning{ + Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + } + for _, tc := range testCases { + // set test + testURL, err := url.Parse(tc.in.uri) + assert.NoError(t, err, tc.desc) + + query := testURL.Query() + + // run + outSignal, outConsent, outErr := parseLegacyGDPRFields(query, tc.in.gppGDPRSignal, tc.in.gppGDPRConsent) + + // assertions + assert.Equal(t, tc.expected.signal, outSignal, tc.desc) + assert.Equal(t, tc.expected.consent, outConsent, tc.desc) + assert.Equal(t, tc.expected.err, outErr, tc.desc) + } +} + +func TestExtractGDPRInfo(t *testing.T) { + type testOutput struct { + requestInfo gdpr.RequestInfo + err error + } + type testCase struct { + desc string + inUri string + expected testOutput + } + testSuite := []struct { + sDesc string + tests []testCase + }{ + { + sDesc: "no gdpr nor gpp values in query", + tests: []testCase{ + { + desc: "expect blank consent, signalNo and nil error", + inUri: "/setuid?bidder=pubmatic&uid=123", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalAmbiguous, + }, + err: nil, + }, + }, + }, + }, + { + sDesc: "missing gpp, gdpr only", + tests: []testCase{ + { + desc: "Invalid gdpr signal value in query, expect blank request info and error", + inUri: "/setuid?gdpr=2", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("the gdpr query param must be either 0 or 1. You gave 2"), + }, + }, + { + desc: "GDPR equals 0, blank consent, expect blank consent, signalNo and nil error", + inUri: "/setuid?gdpr=0", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo}, + err: nil, + }, + }, + { + desc: "GDPR equals 1, blank consent, expect blank request info and error", + inUri: "/setuid?gdpr=1", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("GDPR consent is required when gdpr signal equals 1"), + }, + }, + { + desc: "GDPR equals 0, non-blank consent, expect non-blank request info and nil error", + inUri: "/setuid?gdpr=0&gdpr_consent=someConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "someConsent", + GDPRSignal: gdpr.SignalNo, + }, + err: nil, + }, + }, + { + desc: "GDPR equals 1, non-blank consent, expect non-blank request info and nil error", + inUri: "/setuid?gdpr=1&gdpr_consent=someConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "someConsent", + GDPRSignal: gdpr.SignalYes, + }, + err: nil, + }, + }, + }, + }, + { + sDesc: "missing gdpr, gpp only", + tests: []testCase{ + { + desc: "Malformed GPP_SID string, expect blank request info and error", + inUri: "/setuid?gpp_sid=malformed", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"), + }, + }, + { + desc: "Valid GPP_SID string but invalid GPP string in query, expect blank request info and error", + inUri: "/setuid?gpp=malformed&gpp_sid=2", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), + }, + }, + { + desc: "SectionTCFEU2 not found in GPP string, expect blank consent and signalAmbiguous", + inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalAmbiguous, + }, + err: nil, + }, + }, + { + desc: "No GPP string, nor SectionTCFEU2 found in SID list in query, expect blank consent and signalAmbiguous", + inUri: "/setuid?gpp_sid=3,6", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalNo, + }, + err: nil, + }, + }, + { + desc: "No GPP string, SectionTCFEU2 found in SID list in query, expect blank request info and error", + inUri: "/setuid?gpp_sid=2", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("GDPR consent is required when gdpr signal equals 1"), + }, + }, + { + desc: "SectionTCFEU2 only found in SID list, expect blank request info and error", + inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, + err: errors.New("GDPR consent is required when gdpr signal equals 1"), + }, + }, + { + desc: "SectionTCFEU2 found in GPP string but SID list is nil, expect valid consent and SignalAmbiguous", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalAmbiguous, + }, + err: nil, + }, + }, + { + desc: "SectionTCFEU2 found in GPP string but not in the non-nil SID list, expect valid consent and signalNo", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalNo, + }, + err: nil, + }, + }, + { + desc: "SectionTCFEU2 found both in GPP string and SID list, expect valid consent and signalYes", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalYes, + }, + err: nil, + }, + }, + }, + }, + { + sDesc: "GPP values take priority over GDPR", + tests: []testCase{ + { + desc: "SignalNo in gdpr field but SignalYes in SID list, CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA consent in gpp but legacyConsent in gdpr_consent, expect GPP values to prevail", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0&gdpr_consent=legacyConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalYes, + }, + err: &errortypes.Warning{ + Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + { + desc: "SignalNo in gdpr field but SignalYes in SID list because SectionTCFEU2 is listed, expect GPP to prevail", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalYes, + }, + err: &errortypes.Warning{ + Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + { + desc: "No gpp string in URL query, use gdpr_consent and SignalYes found in SID list because SectionTCFEU2 is listed", + inUri: "/setuid?gpp_sid=2,4&gdpr_consent=legacyConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalAmbiguous, + }, + err: errors.New("GDPR consent is required when gdpr signal equals 1"), + }, + }, + { + desc: "SectionTCFEU2 not found in GPP string but found in SID list, choose the GDPR_CONSENT and GPP_SID signal", + inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2&gdpr=0&gdpr_consent=legacyConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalAmbiguous, + }, + err: errors.New("GDPR consent is required when gdpr signal equals 1"), + }, + }, + { + desc: "SectionTCFEU2 found in GPP string but not in SID list, choose GDPR signal GPP consent value", + inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", + GDPRSignal: gdpr.SignalNo, + }, + err: &errortypes.Warning{ + Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + { + desc: "SectionTCFEU2 not found in GPP, use GDPR_CONSENT value. SignalYes found in gdpr field, but not in the valid SID list, expect SignalNo", + inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent", + expected: testOutput{ + requestInfo: gdpr.RequestInfo{ + Consent: "", + GDPRSignal: gdpr.SignalNo, + }, + err: &errortypes.Warning{ + Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", + WarningCode: errortypes.UnknownWarningCode, + }, + }, + }, + }, + }, + } + + for _, ts := range testSuite { + for _, tc := range ts.tests { + // set test + testURL, err := url.Parse(tc.inUri) + assert.NoError(t, err, tc.desc) + + query := testURL.Query() + + // run + outReqInfo, outErr := extractGDPRInfo(query) + + // assertions + assert.Equal(t, tc.expected.requestInfo, outReqInfo, tc.desc) + assert.Equal(t, tc.expected.err, outErr, tc.desc) + } + } +} + func TestSetUIDEndpointMetrics(t *testing.T) { cookieWithOptOut := usersync.NewCookie() cookieWithOptOut.SetOptOut(true) @@ -301,7 +1125,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { cfgAccountRequired bool expectedResponseCode int expectedMetrics func(*metrics.MetricsEngineMock) - expectedAnalytics func(*MockAnalytics) + expectedAnalytics func(*MockAnalyticsRunner) }{ { description: "Success - Sync", @@ -314,7 +1138,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 200, Bidder: "pubmatic", @@ -336,7 +1160,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { m.On("RecordSetUid", metrics.SetUidOK).Once() m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 200, Bidder: "pubmatic", @@ -357,7 +1181,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidOptOut).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 401, Bidder: "", @@ -378,7 +1202,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "", @@ -399,7 +1223,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -420,12 +1244,12 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidBadRequest).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", UID: "", - Errors: []error{errors.New("gdpr_consent is required when gdpr=1")}, + Errors: []error{errors.New("GDPR consent is required when gdpr signal equals 1")}, Success: false, } a.On("LogSetUIDObject", &expected).Once() @@ -441,7 +1265,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 451, Bidder: "pubmatic", @@ -452,27 +1276,6 @@ func TestSetUIDEndpointMetrics(t *testing.T) { a.On("LogSetUIDObject", &expected).Once() }, }, - { - description: "Blocked account", - uri: "/setuid?bidder=pubmatic&uid=123&account=blocked_acct", - cookies: []*usersync.Cookie{}, - syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, - gdprAllowsHostCookies: true, - expectedResponseCode: 400, - expectedMetrics: func(m *metrics.MetricsEngineMock) { - m.On("RecordSetUid", metrics.SetUidAccountBlocked).Once() - }, - expectedAnalytics: func(a *MockAnalytics) { - expected := analytics.SetUIDObject{ - Status: 400, - Bidder: "pubmatic", - UID: "", - Errors: []error{errCookieSyncAccountBlocked}, - Success: false, - } - a.On("LogSetUIDObject", &expected).Once() - }, - }, { description: "Invalid account", uri: "/setuid?bidder=pubmatic&uid=123&account=unknown", @@ -484,7 +1287,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -506,7 +1309,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { expectedMetrics: func(m *metrics.MetricsEngineMock) { m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", @@ -526,14 +1329,14 @@ func TestSetUIDEndpointMetrics(t *testing.T) { cfgAccountRequired: true, expectedResponseCode: 400, expectedMetrics: func(m *metrics.MetricsEngineMock) { - m.On("RecordSetUid", metrics.SetUidBadRequest).Once() + m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() }, - expectedAnalytics: func(a *MockAnalytics) { + expectedAnalytics: func(a *MockAnalyticsRunner) { expected := analytics.SetUIDObject{ Status: 400, Bidder: "pubmatic", UID: "", - Errors: []error{errors.New("unexpected end of JSON input")}, + Errors: []error{errCookieSyncAccountConfigMalformed}, Success: false, } a.On("LogSetUIDObject", &expected).Once() @@ -542,7 +1345,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { } for _, test := range testCases { - analyticsEngine := &MockAnalytics{} + analyticsEngine := &MockAnalyticsRunner{} test.expectedAnalytics(analyticsEngine) metricsEngine := &metrics.MetricsEngineMock{} @@ -552,7 +1355,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { for _, v := range test.cookies { addCookie(req, v) } - response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired) + response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired, 0, nil, "") assert.Equal(t, test.expectedResponseCode, response.Code, test.description) analyticsEngine.AssertExpectations(t) @@ -566,9 +1369,9 @@ func TestOptedOut(t *testing.T) { cookie.SetOptOut(true) addCookie(request, cookie) syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} - analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) + analytics := analyticsBuild.New(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} - response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false) + response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil, "") assert.Equal(t, http.StatusUnauthorized, response.Code) } @@ -664,6 +1467,30 @@ func TestGetResponseFormat(t *testing.T) { expectedFormat: "i", description: "parameter given is empty (by empty item), use default sync type redirect", }, + { + urlValues: url.Values{"f": []string{""}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "i", + description: "parameter given is empty (by empty item), use default sync type redirect", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "i"}, + expectedFormat: "i", + description: "format not provided, but formatOverride is defined, expect i", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "b"}, + expectedFormat: "b", + description: "format not provided, but formatOverride is defined, expect b", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "b", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "b", + description: "format not provided, default is defined but formatOverride is defined as well, expect b", + }, } for _, test := range testCases { @@ -679,6 +1506,65 @@ func TestGetResponseFormat(t *testing.T) { } } +func TestIsSyncerPriority(t *testing.T) { + testCases := []struct { + name string + givenBidderNameFromSyncerQuery string + givenPriorityGroups [][]string + expected bool + }{ + { + name: "priority-tier-1", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{{"a"}}, + expected: true, + }, + { + name: "priority-tier-other", + givenBidderNameFromSyncerQuery: "c", + givenPriorityGroups: [][]string{{"a"}, {"b", "c"}}, + expected: true, + }, + { + name: "priority-case-insensitive", + givenBidderNameFromSyncerQuery: "A", + givenPriorityGroups: [][]string{{"a"}}, + expected: true, + }, + { + name: "not-priority-empty", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{}, + expected: false, + }, + { + name: "not-priority-not-defined", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{{"b"}}, + expected: false, + }, + { + name: "no-bidder", + givenBidderNameFromSyncerQuery: "", + givenPriorityGroups: [][]string{{"b"}}, + expected: false, + }, + { + name: "no-priority-groups", + givenBidderNameFromSyncerQuery: "a", + givenPriorityGroups: [][]string{}, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + isPriority := isSyncerPriority(test.givenBidderNameFromSyncerQuery, test.givenPriorityGroups) + assert.Equal(t, test.expected, isPriority) + }) + } +} + func assertHasSyncs(t *testing.T, testCase string, resp *httptest.ResponseRecorder, syncs map[string]string) { t.Helper() cookie := parseCookieString(t, resp) @@ -697,20 +1583,23 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { if len(existingSyncs) > 0 { pbsCookie := usersync.NewCookie() for key, value := range existingSyncs { - pbsCookie.TrySync(key, value) + pbsCookie.Sync(key, value) } addCookie(request, pbsCookie) } return request } -func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool) *httptest.ResponseRecorder { +func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string, formatOverride string) *httptest.ResponseRecorder { cfg := config.Configuration{ AccountRequired: cfgAccountRequired, - BlacklistedAcctMap: map[string]bool{ - "blocked_acct": true, - }, AccountDefaults: config.Account{}, + UserSync: config.UserSync{ + PriorityGroups: priorityGroups, + }, + HostCookie: config.HostCookie{ + MaxCookieSizeBytes: maxCookieSize, + }, } cfg.MarshalAccountDefaults() @@ -732,7 +1621,11 @@ func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metric syncersByBidder := make(map[string]usersync.Syncer) for bidderName, syncerKey := range syncersBidderNameToKey { - syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame} + syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame, formatOverride: formatOverride} + if priorityGroups == nil { + cfg.UserSync.PriorityGroups = [][]string{{}} + cfg.UserSync.PriorityGroups[0] = append(cfg.UserSync.PriorityGroups[0], bidderName) + } } fakeAccountsFetcher := FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ @@ -740,6 +1633,10 @@ func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metric "disabled_acct": json.RawMessage(`{"disabled":true}`), "malformed_acct": json.RawMessage(`{"disabled":"malformed"}`), "invalid_json_acct": json.RawMessage(`{"}`), + + "valid_acct_with_valid_activities_usersync_enabled": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": true}}}}`), + "valid_acct_with_valid_activities_usersync_disabled": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": false}}}}`), + "valid_acct_with_invalid_activities": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`), }} endpoint := NewSetUIDEndpoint(&cfg, syncersByBidder, gdprPermsBuilder, tcf2ConfigBuilder, analytics, fakeAccountsFetcher, metrics) @@ -749,10 +1646,12 @@ func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metric } func addCookie(req *http.Request, cookie *usersync.Cookie) { - req.AddCookie(cookie.ToHTTPCookie(time.Duration(1) * time.Hour)) + httpCookie, _ := ToHTTPCookie(cookie) + req.AddCookie(httpCookie) } func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *usersync.Cookie { + decoder := usersync.Base64Decoder{} cookieString := response.Header().Get("Set-Cookie") parser := regexp.MustCompile("uids=(.*?);") res := parser.FindStringSubmatch(cookieString) @@ -761,7 +1660,7 @@ func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *users Name: "uids", Value: res[1], } - return usersync.ParseCookie(&httpCookie) + return decoder.Decode(httpCookie.Value) } type fakePermissionsBuilder struct { @@ -813,20 +1712,57 @@ func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCo type fakeSyncer struct { key string defaultSyncType usersync.SyncType + formatOverride string } func (s fakeSyncer) Key() string { return s.key } -func (s fakeSyncer) DefaultSyncType() usersync.SyncType { - return s.defaultSyncType +func (s fakeSyncer) DefaultResponseFormat() usersync.SyncType { + switch s.formatOverride { + case "b": + return usersync.SyncTypeIFrame + case "i": + return usersync.SyncTypeRedirect + default: + return s.defaultSyncType + } } func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool { return true } -func (s fakeSyncer) GetSync(syncTypes []usersync.SyncType, privacyPolicies privacy.Policies) (usersync.Sync, error) { +func (s fakeSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros.UserSyncPrivacy) (usersync.Sync, error) { return usersync.Sync{}, nil } + +func ToHTTPCookie(cookie *usersync.Cookie) (*http.Cookie, error) { + encoder := usersync.Base64Encoder{} + encodedCookie, err := encoder.Encode(cookie) + if err != nil { + return nil, nil + } + + return &http.Cookie{ + Name: uidCookieName, + Value: encodedCookie, + Expires: time.Now().Add((90 * 24 * time.Hour)), + Path: "/", + }, nil +} + +func getUIDFromHeader(setCookieHeader string) string { + cookies := strings.Split(setCookieHeader, ";") + for _, cookie := range cookies { + trimmedCookie := strings.TrimSpace(cookie) + if strings.HasPrefix(trimmedCookie, "uids=") { + parts := strings.SplitN(trimmedCookie, "=", 2) + if len(parts) == 2 { + return parts[1] + } + } + } + return "" +} diff --git a/endpoints/version.go b/endpoints/version.go index 00d894963e6..f9e07da9a0d 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const versionEndpointValueNotSet = "not-set" @@ -29,7 +30,7 @@ func prepareVersionEndpointResponse(version, revision string) (json.RawMessage, revision = versionEndpointValueNotSet } - return json.Marshal(struct { + return jsonutil.Marshal(struct { Revision string `json:"revision"` Version string `json:"version"` }{ diff --git a/errortypes/code.go b/errortypes/code.go index c68eb19607a..1de5e648cef 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -9,11 +9,14 @@ const ( BadServerResponseErrorCode FailedToRequestBidsErrorCode BidderTemporarilyDisabledErrorCode - BlacklistedAcctErrorCode + AccountDisabledErrorCode AcctRequiredErrorCode NoConversionRateErrorCode MalformedAcctErrorCode ModuleRejectionErrorCode + TmaxTimeoutErrorCode + FailedToMarshalErrorCode + FailedToUnmarshalErrorCode ) // Defines numeric codes for well-known warnings. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index d93075b7c6c..d31c4166b06 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -20,6 +20,25 @@ func (err *Timeout) Severity() Severity { return SeverityFatal } +// TmaxTimeout should be used to flag that remaining tmax duration is not enough to get response from bidder +// +// TmaxTimeout will not be written to the app log, since it's not an actionable item for the Prebid Server hosts. +type TmaxTimeout struct { + Message string +} + +func (err *TmaxTimeout) Error() string { + return err.Message +} + +func (err *TmaxTimeout) Code() int { + return TmaxTimeoutErrorCode +} + +func (err *TmaxTimeout) Severity() Severity { + return SeverityFatal +} + // BadInput should be used when returning errors which are caused by bad input. // It should _not_ be used if the error is a server-side issue (e.g. failed to send the external request). // @@ -60,23 +79,20 @@ func (err *BlacklistedApp) Severity() Severity { return SeverityFatal } -// BlacklistedAcct should be used when a request account ID matches an entry in the BlacklistedAccts -// environment variable array -// -// These errors will be written to http.ResponseWriter before canceling execution -type BlacklistedAcct struct { +// AccountDisabled should be used when a request an account is specifically disabled in account config. +type AccountDisabled struct { Message string } -func (err *BlacklistedAcct) Error() string { +func (err *AccountDisabled) Error() string { return err.Message } -func (err *BlacklistedAcct) Code() int { - return BlacklistedAcctErrorCode +func (err *AccountDisabled) Code() int { + return AccountDisabledErrorCode } -func (err *BlacklistedAcct) Severity() Severity { +func (err *AccountDisabled) Severity() Severity { return SeverityFatal } @@ -183,7 +199,8 @@ func (err *MalformedAcct) Severity() Severity { return SeverityFatal } -// Warning is a generic non-fatal error. +// Warning is a generic non-fatal error. Throughout the codebase, an error can +// only be a warning if it's of the type defined below type Warning struct { Message string WarningCode int @@ -200,3 +217,37 @@ func (err *Warning) Code() int { func (err *Warning) Severity() Severity { return SeverityWarning } + +// FailedToUnmarshal should be used to represent errors that occur when unmarshaling raw json. +type FailedToUnmarshal struct { + Message string +} + +func (err *FailedToUnmarshal) Error() string { + return err.Message +} + +func (err *FailedToUnmarshal) Code() int { + return FailedToUnmarshalErrorCode +} + +func (err *FailedToUnmarshal) Severity() Severity { + return SeverityFatal +} + +// FailedToMarshal should be used to represent errors that occur when marshaling to a byte slice. +type FailedToMarshal struct { + Message string +} + +func (err *FailedToMarshal) Error() string { + return err.Message +} + +func (err *FailedToMarshal) Code() int { + return FailedToMarshalErrorCode +} + +func (err *FailedToMarshal) Severity() Severity { + return SeverityFatal +} diff --git a/errortypes/severity.go b/errortypes/severity.go index 0838b09592e..5f9cd80dd28 100644 --- a/errortypes/severity.go +++ b/errortypes/severity.go @@ -20,7 +20,10 @@ func isFatal(err error) bool { return !ok || s.Severity() == SeverityFatal } -func isWarning(err error) bool { +// IsWarning returns true if an error is labeled with a Severity of SeverityWarning +// Throughout the codebase, errors with SeverityWarning are of the type Warning +// defined in this package +func IsWarning(err error) bool { s, ok := err.(Coder) return ok && s.Severity() == SeverityWarning } @@ -54,7 +57,7 @@ func WarningOnly(errs []error) []error { errsWarning := make([]error, 0, len(errs)) for _, err := range errs { - if isWarning(err) { + if IsWarning(err) { errsWarning = append(errsWarning, err) } } diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index d8f683b35d2..cd70530bfc3 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -4,10 +4,10 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) { @@ -39,6 +39,13 @@ func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName] continue } + if len(info.AliasOf) > 0 { + if err := setAliasBuilder(info, builders, bidderName); err != nil { + errs = append(errs, fmt.Errorf("%v: failed to set alias builder: %v", bidder, err)) + continue + } + } + builder, builderFound := builders[bidderName] if !builderFound { errs = append(errs, fmt.Errorf("%v: builder not registered", bidder)) @@ -59,6 +66,20 @@ func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName] return bidders, errs } +func setAliasBuilder(info config.BidderInfo, builders map[openrtb_ext.BidderName]adapters.Builder, bidderName openrtb_ext.BidderName) error { + parentBidderName, parentBidderFound := openrtb_ext.NormalizeBidderName(info.AliasOf) + if !parentBidderFound { + return fmt.Errorf("unknown parent bidder: %v for alias: %v", info.AliasOf, bidderName) + } + + builder, builderFound := builders[parentBidderName] + if !builderFound { + return fmt.Errorf("%v: parent builder not registered", parentBidderName) + } + builders[bidderName] = builder + return nil +} + func buildAdapterInfo(bidderInfo config.BidderInfo) config.Adapter { adapter := config.Adapter{} adapter.Endpoint = bidderInfo.Endpoint @@ -82,19 +103,31 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam return activeBidders } -// GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders. -func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string { - disabledBidders := map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, +func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]string { + removed := map[string]string{ + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, + "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, + "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`, + "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, + "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, + "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, + "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, + "brightroll": `Bidder "brightroll" is no longer available in Prebid Server. Please update your configuration.`, + "engagebdr": `Bidder "engagebdr" is no longer available in Prebid Server. Please update your configuration.`, + "ninthdecimal": `Bidder "ninthdecimal" is no longer available in Prebid Server. Please update your configuration.`, + "kubient": `Bidder "kubient" is no longer available in Prebid Server. Please update your configuration.`, + "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, + "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, + "nanointeractive": `Bidder "nanointeractive" is no longer available in Prebid Server. Please update your configuration.`, } + return mergeRemovedAndDisabledBidderWarningMessages(removed, infos) +} + +func mergeRemovedAndDisabledBidderWarningMessages(removed map[string]string, infos config.BidderInfos) map[string]string { + disabledBidders := removed + for name, info := range infos { if info.Disabled { msg := fmt.Sprintf(`Bidder "%s" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, name) diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 9d2a5c3cd5f..8f765305248 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -6,13 +6,14 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/config" - metrics "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/appnexus" + "github.com/prebid/prebid-server/v2/adapters/rubicon" + "github.com/prebid/prebid-server/v2/config" + metrics "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -163,6 +164,55 @@ func TestBuildBidders(t *testing.T) { } } +func TestSetAliasBuilder(t *testing.T) { + rubiconBidder := fakeBidder{"b"} + ixBidder := fakeBidder{"ix"} + rubiconBuilder := fakeBuilder{rubiconBidder, nil}.Builder + ixBuilder := fakeBuilder{ixBidder, nil}.Builder + + testCases := []struct { + description string + bidderInfo config.BidderInfo + builders map[openrtb_ext.BidderName]adapters.Builder + bidderName openrtb_ext.BidderName + expectedBuilders map[openrtb_ext.BidderName]adapters.Builder + expectedError error + }{ + { + description: "Success - Alias builder", + bidderInfo: config.BidderInfo{Disabled: false, AliasOf: "rubicon"}, + bidderName: openrtb_ext.BidderName("appnexus"), + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderRubicon: rubiconBuilder}, + expectedBuilders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderRubicon: rubiconBuilder, openrtb_ext.BidderAppnexus: rubiconBuilder}, + }, + { + description: "Failure - Invalid parent bidder builder", + bidderInfo: config.BidderInfo{Disabled: false, AliasOf: "rubicon"}, + bidderName: openrtb_ext.BidderName("appnexus"), + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderIx: ixBuilder}, + expectedError: errors.New("rubicon: parent builder not registered"), + }, + { + description: "Failure - Invalid parent for alias", + bidderInfo: config.BidderInfo{Disabled: false, AliasOf: "unknown"}, + bidderName: openrtb_ext.BidderName("appnexus"), + builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderIx: ixBuilder}, + expectedError: errors.New("unknown parent bidder: unknown for alias: appnexus"), + }, + } + + for _, test := range testCases { + err := setAliasBuilder(test.bidderInfo, test.builders, test.bidderName) + + if test.expectedBuilders != nil { + assert.ObjectsAreEqual(test.builders, test.expectedBuilders) + } + if test.expectedError != nil { + assert.EqualError(t, test.expectedError, err.Error(), test.description+":errors") + } + } +} + func TestGetActiveBidders(t *testing.T) { testCases := []struct { description string @@ -197,75 +247,71 @@ func TestGetActiveBidders(t *testing.T) { } } -func TestGetDisabledBiddersErrorMessages(t *testing.T) { +func TestGetDisabledBidderWarningMessages(t *testing.T) { + t.Run("removed", func(t *testing.T) { + result := GetDisabledBidderWarningMessages(nil) + + // test proper construction by verifying one expected bidder is in the list + require.Contains(t, result, "groupm") + assert.Equal(t, result["groupm"], `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`) + }) + + t.Run("removed-and-disabled", func(t *testing.T) { + result := GetDisabledBidderWarningMessages(map[string]config.BidderInfo{"bidderA": infoDisabled}) + + // test proper construction by verifying one expected bidder is in the list with the disabled bidder + require.Contains(t, result, "groupm") + assert.Equal(t, result["groupm"], `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`) + + require.Contains(t, result, "bidderA") + assert.Equal(t, result["bidderA"], `Bidder "bidderA" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`) + }) +} + +func TestMergeRemovedAndDisabledBidderWarningMessages(t *testing.T) { testCases := []struct { - description string - bidderInfos map[string]config.BidderInfo - expected map[string]string + name string + givenRemoved map[string]string + givenBidderInfos map[string]config.BidderInfo + expected map[string]string }{ { - description: "None", - bidderInfos: map[string]config.BidderInfo{}, - expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, - }, + name: "none", + givenRemoved: map[string]string{}, + givenBidderInfos: map[string]config.BidderInfo{}, + expected: map[string]string{}, }, { - description: "Enabled", - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, - }, + name: "removed", + givenRemoved: map[string]string{"bidderA": `Bidder A Message`}, + givenBidderInfos: map[string]config.BidderInfo{}, + expected: map[string]string{"bidderA": `Bidder A Message`}, }, { - description: "Disabled", - bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled}, - expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`, - "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, - }, + name: "enabled", + givenRemoved: map[string]string{}, + givenBidderInfos: map[string]config.BidderInfo{"bidderA": infoEnabled}, + expected: map[string]string{}, }, { - description: "Mixed", - bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled}, - expected: map[string]string{ - "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, - "adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`, - "somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`, - "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`, - "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, - "andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`, - "oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`, - "groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`, - "verizonmedia": `Bidder "verizonmedia" is no longer available in Prebid Server. Please update your configuration.`, - }, + name: "disabled", + givenRemoved: map[string]string{}, + givenBidderInfos: map[string]config.BidderInfo{"bidderA": infoDisabled}, + expected: map[string]string{"bidderA": `Bidder "bidderA" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`}, + }, + { + name: "mixed", + givenRemoved: map[string]string{"bidderA": `Bidder A Message`}, + givenBidderInfos: map[string]config.BidderInfo{"bidderB": infoEnabled, "bidderC": infoDisabled}, + expected: map[string]string{"bidderA": `Bidder A Message`, "bidderC": `Bidder "bidderC" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`}, }, } for _, test := range testCases { - result := GetDisabledBiddersErrorMessages(test.bidderInfos) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + result := mergeRemovedAndDisabledBidderWarningMessages(test.givenRemoved, test.givenBidderInfos) + assert.Equal(t, test.expected, result, test.name) + }) } } diff --git a/exchange/auction.go b/exchange/auction.go index b2d6eb571ca..17a843fb5ed 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -2,7 +2,6 @@ package exchange import ( "context" - "encoding/json" "encoding/xml" "errors" "fmt" @@ -13,10 +12,11 @@ import ( uuid "github.com/gofrs/uuid" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const ( @@ -83,7 +83,7 @@ func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout in d.CacheKey = rawUUID.String() } - data, err := json.Marshal(d.CacheString) + data, err := jsonutil.Marshal(d.CacheString) if err != nil { return err } @@ -244,7 +244,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } if bids { - if jsonBytes, err := json.Marshal(topBid.Bid); err == nil { + if jsonBytes, err := jsonutil.Marshal(topBid.Bid); err == nil { jsonBytes, err = evTracking.modifyBidJSON(topBid, bidderName, jsonBytes) if err != nil { errs = append(errs, err) @@ -265,7 +265,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } if vast && topBid.BidType == openrtb_ext.BidTypeVideo { vastXML := makeVAST(topBid.Bid) - if jsonBytes, err := json.Marshal(vastXML); err == nil { + if jsonBytes, err := jsonutil.Marshal(vastXML); err == nil { if useCustomCacheKey { toCache = append(toCache, prebid_cache_client.Cacheable{ Type: prebid_cache_client.TypeXML, @@ -292,7 +292,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden { debugLog.CacheKey = hbCacheID debugLog.BuildCacheString() - if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { + if jsonBytes, err := jsonutil.Marshal(debugLog.CacheString); err == nil { toCache = append(toCache, prebid_cache_client.Cacheable{ Type: debugLog.CacheType, Data: jsonBytes, diff --git a/exchange/auction_response.go b/exchange/auction_response.go index 3b85a4472c2..c92798d0f3b 100644 --- a/exchange/auction_response.go +++ b/exchange/auction_response.go @@ -2,7 +2,7 @@ package exchange import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AuctionResponse contains OpenRTB Bid Response object and its extension (un-marshalled) object diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ad0f8d4a72c..10c4b9a5e67 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -2,7 +2,6 @@ package exchange import ( "context" - "encoding/json" "encoding/xml" "fmt" "os" @@ -13,11 +12,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -179,7 +179,7 @@ func loadCacheSpec(filename string) (*cacheSpec, error) { } var spec cacheSpec - if err := json.Unmarshal(specData, &spec); err != nil { + if err := jsonutil.UnmarshalValid(specData, &spec); err != nil { return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err) } @@ -286,18 +286,18 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { for i, expectedCacheable := range specData.ExpectedCacheables { found := false var expectedData interface{} - if err := json.Unmarshal(expectedCacheable.Data, &expectedData); err != nil { + if err := jsonutil.UnmarshalValid(expectedCacheable.Data, &expectedData); err != nil { t.Fatalf("Failed to decode expectedCacheables[%d].value: %v", i, err) } if s, ok := expectedData.(string); ok && expectedCacheable.Type == prebid_cache_client.TypeJSON { // decode again if we have pre-encoded json string values - if err := json.Unmarshal([]byte(s), &expectedData); err != nil { + if err := jsonutil.UnmarshalValid([]byte(s), &expectedData); err != nil { t.Fatalf("Failed to re-decode expectedCacheables[%d].value :%v", i, err) } } for j, cachedItem := range cache.items { var actualData interface{} - if err := json.Unmarshal(cachedItem.Data, &actualData); err != nil { + if err := jsonutil.UnmarshalValid(cachedItem.Data, &actualData); err != nil { t.Fatalf("Failed to decode actual cache[%d].value: %s", j, err) } if assert.ObjectsAreEqual(expectedData, actualData) && diff --git a/exchange/bidder.go b/exchange/bidder.go index f5efcff3edd..ce502e53d84 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -16,23 +16,24 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config/util" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/config/util" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/version" "github.com/prebid/openrtb/v19/adcom1" nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "golang.org/x/net/context/ctxhttp" ) @@ -58,15 +59,27 @@ type AdaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) + requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) } // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters type bidRequestOptions struct { - accountDebugAllowed bool - headerDebugAllowed bool - addCallSignHeader bool - bidAdjustments map[string]float64 + accountDebugAllowed bool + headerDebugAllowed bool + addCallSignHeader bool + bidAdjustments map[string]float64 + tmaxAdjustments *TmaxAdjustmentsPreprocessed + bidderRequestStartTime time.Time +} + +type extraBidderRespInfo struct { + respProcessingStartTime time.Time +} + +type extraAuctionResponseInfo struct { + fledge *openrtb_ext.Fledge + bidsFound bool + bidderResponseStartTime time.Time } const ImpIdReqBody = "Stored bid response for impression id: " @@ -117,21 +130,32 @@ type bidderAdapterConfig struct { EndpointCompression string } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) { - reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) +func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { + request := openrtb_ext.RequestWrapper{BidRequest: bidderRequest.BidRequest} + reject := hookExecutor.ExecuteBidderRequestStage(&request, string(bidderRequest.BidderName)) if reject != nil { - return nil, []error{reject} + return nil, extraBidderRespInfo{}, []error{reject} } var ( reqData []*adapters.RequestData errs []error responseChannel chan *httpCallInfo + extraRespInfo extraBidderRespInfo ) + // rebuild request after modules execution + request.RebuildRequest() + bidderRequest.BidRequest = request.BidRequest + //check if real request exists for this bidder or it only has stored responses dataLen := 0 if len(bidderRequest.BidRequest.Imp) > 0 { + // Reducing the amount of time bidders have to compensate for the processing time used by PBS to fetch a stored request (if needed), validate the OpenRTB request and split it into multiple requests sanitized for each bidder + // As well as for the time needed by PBS to prepare the auction response + if bidRequestOptions.tmaxAdjustments != nil && bidRequestOptions.tmaxAdjustments.IsEnforced { + bidderRequest.BidRequest.TMax = getBidderTmax(&bidderTmaxCtx{ctx}, bidderRequest.BidRequest.TMax, *bidRequestOptions.tmaxAdjustments) + } reqData, errs = bidder.Bidder.MakeRequests(bidderRequest.BidRequest, reqInfo) if len(reqData) == 0 { @@ -139,7 +163,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if len(errs) == 0 { errs = append(errs, &errortypes.FailedToRequestBids{Message: "The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}) } - return nil, errs + return nil, extraBidderRespInfo{}, errs } xPrebidHeader := version.BuildXPrebidHeaderForRequest(bidderRequest.BidRequest, version.Ver) @@ -173,11 +197,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde dataLen = len(reqData) + len(bidderRequest.BidderStoredResponses) responseChannel = make(chan *httpCallInfo, dataLen) if len(reqData) == 1 { - responseChannel <- bidder.doRequest(ctx, reqData[0], reqInfo.BidderRequestStartTime) + responseChannel <- bidder.doRequest(ctx, reqData[0], bidRequestOptions.bidderRequestStartTime, bidRequestOptions.tmaxAdjustments) } else { for _, oneReqData := range reqData { go func(data *adapters.RequestData) { - responseChannel <- bidder.doRequest(ctx, data, reqInfo.BidderRequestStartTime) + responseChannel <- bidder.doRequest(ctx, data, bidRequestOptions.bidderRequestStartTime, bidRequestOptions.tmaxAdjustments) }(oneReqData) // Method arg avoids a race condition on oneReqData } } @@ -231,7 +255,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } if httpInfo.err == nil { - startTime := time.Now() + extraRespInfo.respProcessingStartTime = time.Now() bidResponse, moreErrs := bidder.Bidder.MakeBids(bidderRequest.BidRequest, httpInfo.request, httpInfo.response) errs = append(errs, moreErrs...) @@ -269,7 +293,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde errs = append(errs, moreErrs...) if nativeMarkup != nil { - markup, err := json.Marshal(*nativeMarkup) + markup, err := jsonutil.Marshal(*nativeMarkup) if err != nil { errs = append(errs, err) } else { @@ -308,10 +332,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { - if bidResponse.Bids[i].BidMeta == nil { - bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} - } - bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() bidderName := bidderRequest.BidderName if bidResponse.Bids[i].Seat != "" { @@ -330,9 +350,9 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } adjustmentFactor := 1.0 - if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderName.String()]; ok { + if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderName.String()))]; ok { adjustmentFactor = givenAdjustment - } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[bidderRequest.BidderName.String()]; ok { + } else if givenAdjustment, ok := bidRequestOptions.bidAdjustments[(strings.ToLower(bidderRequest.BidderName.String()))]; ok { adjustmentFactor = givenAdjustment } @@ -373,24 +393,22 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde errs = append(errs, err) } } - reqInfo.MakeBidsTimeInfo.Durations = append(reqInfo.MakeBidsTimeInfo.Durations, time.Since(startTime)) } else { errs = append(errs, httpInfo.err) } } - reqInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime = time.Now() seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap)) for _, seatBid := range seatBidMap { seatBids = append(seatBids, seatBid) } - return seatBids, errs + return seatBids, extraRespInfo, errs } func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { var errs []error - var nativeMarkup *nativeResponse.Response - if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { + var nativeMarkup nativeResponse.Response + if err := jsonutil.UnmarshalValid(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { // Some bidders are returning non-IAB compliant native markup. In this case Prebid server will not be able to add types. E.g Facebook return nil, errs } @@ -402,7 +420,7 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes } var nativePayload nativeRequests.Request - if err := json.Unmarshal(json.RawMessage((*nativeImp).Request), &nativePayload); err != nil { + if err := jsonutil.UnmarshalValid(json.RawMessage((*nativeImp).Request), &nativePayload); err != nil { errs = append(errs, err) } @@ -412,7 +430,7 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes } } - return nativeMarkup, errs + return &nativeMarkup, errs } func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { @@ -498,11 +516,11 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { // doRequest makes a request, handles the response, and returns the data needed by the // Bidder interface. -func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, pbsRequestStartTime time.Time) *httpCallInfo { - return bidder.doRequestImpl(ctx, req, glog.Warningf, pbsRequestStartTime) +func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { + return bidder.doRequestImpl(ctx, req, glog.Warningf, bidderRequestStartTime, tmaxAdjustments) } -func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, pbsRequestStartTime time.Time) *httpCallInfo { +func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { var requestBody []byte switch strings.ToUpper(bidder.config.EndpointCompression) { @@ -526,7 +544,18 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re if !bidder.config.DisableConnMetrics { ctx = bidder.addClientTrace(ctx) } - bidder.me.RecordOverheadTime(metrics.PreBidder, time.Since(pbsRequestStartTime)) + bidder.me.RecordOverheadTime(metrics.PreBidder, time.Since(bidderRequestStartTime)) + + if tmaxAdjustments != nil && tmaxAdjustments.IsEnforced { + if hasShorterDurationThanTmax(&bidderTmaxCtx{ctx}, *tmaxAdjustments) { + bidder.me.RecordTMaxTimeout() + return &httpCallInfo{ + request: req, + err: &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, + } + } + } + httpCallStart := time.Now() httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq) if err != nil { @@ -609,7 +638,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou } } } else if bidder.config.Debug.TimeoutNotification.Log { - reqJSON, err := json.Marshal(req) + reqJSON, err := jsonutil.Marshal(req) var msg string if err == nil { msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request(%s)", errL[0].Error(), string(reqJSON)) @@ -709,3 +738,16 @@ func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []o } return string(bidType) } + +func hasShorterDurationThanTmax(ctx bidderTmaxContext, tmaxAdjustments TmaxAdjustmentsPreprocessed) bool { + if tmaxAdjustments.IsEnforced { + if deadline, ok := ctx.Deadline(); ok { + overheadNS := time.Duration(tmaxAdjustments.BidderNetworkLatencyBuffer+tmaxAdjustments.PBSResponsePreparationDuration) * time.Millisecond + bidderTmax := deadline.Add(-overheadNS) + + remainingDuration := ctx.Until(bidderTmax).Milliseconds() + return remainingDuration < int64(tmaxAdjustments.BidderResponseDurationMin) + } + } + return false +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 87d04185cba..b2314c8c428 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -21,17 +21,18 @@ import ( nativeRequests "github.com/prebid/openrtb/v19/native1/request" nativeResponse "github.com/prebid/openrtb/v19/native1/response" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -110,11 +111,10 @@ func TestSingleBidder(t *testing.T) { bidAdjustments: bidAdjustments, } extraInfo := &adapters.ExtraRequestInfo{} - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) - assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations) - assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero()) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) seatBid := seatBids[0] // Make sure the goodSingleBidder was called with the expected arguments. @@ -236,10 +236,9 @@ func TestSingleBidderGzip(t *testing.T) { bidAdjustments: bidAdjustments, } extraInfo := &adapters.ExtraRequestInfo{} - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) - assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations) - assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero()) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) seatBid := seatBids[0] // Make sure the goodSingleBidder was called with the expected arguments. @@ -339,7 +338,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { bidAdjustments: bidAdjustments, } extraInfo := &adapters.ExtraRequestInfo{} - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { Uri: server.URL, @@ -352,8 +351,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { assert.Empty(t, errs) assert.Len(t, seatBids, 1) - assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations) - assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero()) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCalls) } @@ -393,7 +391,7 @@ func TestSetGPCHeader(t *testing.T) { bidAdjustments: bidAdjustments, } extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"} - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -407,8 +405,7 @@ func TestSetGPCHeader(t *testing.T) { assert.Empty(t, errs) assert.Len(t, seatBids, 1) - assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations) - assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero()) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall) } @@ -446,7 +443,7 @@ func TestSetGPCHeaderNil(t *testing.T) { bidAdjustments: bidAdjustments, } extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"} - seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -459,9 +456,8 @@ func TestSetGPCHeaderNil(t *testing.T) { } assert.Empty(t, errs) - assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations) - assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero()) assert.Len(t, seatBids, 1) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall) } @@ -518,7 +514,7 @@ func TestMultiBidder(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) if len(seatBids) != 1 { t.Fatalf("SeatBid should exist, because bids exist.") @@ -530,6 +526,7 @@ func TestMultiBidder(t *testing.T) { if len(seatBids[0].Bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) { t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBids[0].Bids)) } + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } @@ -559,11 +556,11 @@ func TestBidderTimeout(t *testing.T) { Client: server.Client(), me: &metricsConfig.NilMetricsEngine{}, } - + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} callInfo := bidder.doRequest(ctx, &adapters.RequestData{ Method: "POST", Uri: server.URL, - }, time.Now()) + }, time.Now(), tmaxAdjustments) if callInfo.err == nil { t.Errorf("The bidder should report an error if the context has expired already.") } @@ -579,10 +576,10 @@ func TestInvalidRequest(t *testing.T) { Bidder: &mixedMultiBidder{}, Client: server.Client(), } - + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{ Method: "\"", // force http.NewRequest() to fail - }, time.Now()) + }, time.Now(), tmaxAdjustments) if callInfo.err == nil { t.Errorf("bidderAdapter.doRequest should return an error if the request data is malformed.") } @@ -602,11 +599,11 @@ func TestConnectionClose(t *testing.T) { BidderName: openrtb_ext.BidderAppnexus, me: &metricsConfig.NilMetricsEngine{}, } - + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{ Method: "POST", Uri: server.URL, - }, time.Now()) + }, time.Now(), tmaxAdjustments) if callInfo.err == nil { t.Errorf("bidderAdapter.doRequest should return an error if the connection closes unexpectedly.") } @@ -860,7 +857,7 @@ func TestMultiCurrencies(t *testing.T) { mockedHTTPServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - b, err := json.Marshal(tc.rates) + b, err := jsonutil.Marshal(tc.rates) if err == nil { rw.WriteHeader(http.StatusOK) rw.Write(b) @@ -885,7 +882,7 @@ func TestMultiCurrencies(t *testing.T) { BidderName: openrtb_ext.BidderAppnexus, } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1} - seatBids, errs := bidder.requestBid( + seatBids, extraBidderRespInfo, errs := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -903,6 +900,7 @@ func TestMultiCurrencies(t *testing.T) { ) assert.Len(t, seatBids, 1) seatBid := seatBids[0] + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) // Verify: resultLightBids := make([]bid, len(seatBid.Bids)) @@ -1045,7 +1043,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1} - seatBids, errs := bidder.requestBid( + seatBids, extraBidderRespInfo, errs := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -1068,6 +1066,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description) assert.Equal(t, tc.expectedBidsCount, uint(len(seatBid.Bids)), tc.description) assert.ElementsMatch(t, tc.expectedBadCurrencyErrors, errs, tc.description) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } } @@ -1179,7 +1178,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { mockedHTTPServer := httptest.NewServer(http.HandlerFunc( func(rw http.ResponseWriter, req *http.Request) { - b, err := json.Marshal(tc.rates) + b, err := jsonutil.Marshal(tc.rates) if err == nil { rw.WriteHeader(http.StatusOK) rw.Write(b) @@ -1224,7 +1223,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1} - seatBids, errs := bidder.requestBid( + seatBids, extraBidderRespInfo, errs := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -1249,6 +1248,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } else { assert.Nil(t, errs, tc.description) assert.Equal(t, tc.expectedPickedCurrency, seatBid.Currency, tc.description) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } } } @@ -1542,7 +1542,7 @@ func TestMobileNativeTypes(t *testing.T) { BidderName: "test", } bidAdjustments := map[string]float64{"test": 1.0} - seatBids, _ := bidder.requestBid( + seatBids, extraBidderRespInfo, _ := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -1559,7 +1559,7 @@ func TestMobileNativeTypes(t *testing.T) { nil, ) assert.Len(t, seatBids, 1) - + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) var actualValue string for _, bid := range seatBids[0].Bids { actualValue = bid.Bid.AdM @@ -1568,6 +1568,46 @@ func TestMobileNativeTypes(t *testing.T) { } } +func TestAddNativeTypes(t *testing.T) { + testCases := []struct { + description string + bidderRequest *openrtb2.BidRequest + bid *openrtb2.Bid + expectedResponse *nativeResponse.Response + expectedErrors []error + }{ + { + description: "Null in bid.Adm in response", + bidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "some-imp-id", + Native: &openrtb2.Native{ + Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}", + }, + }, + }, + App: &openrtb2.App{}, + }, + bid: &openrtb2.Bid{ + ImpID: "some-imp-id", + AdM: "null", + Price: 10, + }, + expectedResponse: nil, + expectedErrors: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.description, func(t *testing.T) { + resp, errs := addNativeTypes(tt.bid, tt.bidderRequest) + assert.Equal(t, tt.expectedResponse, resp, "response") + assert.Equal(t, tt.expectedErrors, errs, "errors") + }) + } +} + func TestRequestBidsStoredBidResponses(t *testing.T) { respBody := "{\"bid\":false}" respStatus := 200 @@ -1662,7 +1702,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { ImpReplaceImpId: tc.impReplaceImpId, } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} - seatBids, _ := bidder.requestBid( + seatBids, extraBidderRespInfo, _ := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -1679,6 +1719,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) { nil, ) assert.Len(t, seatBids, 1) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) assert.Len(t, seatBids[0].Bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description) for _, bid := range seatBids[0].Bids { @@ -1777,7 +1818,7 @@ func TestFledge(t *testing.T) { }, BidderName: "openx", } - seatBids, _ := bidder.requestBid( + seatBids, extraBidderRespInfo, _ := bidder.requestBid( context.Background(), bidderReq, currencyConverter.Rates(), @@ -1798,6 +1839,7 @@ func TestFledge(t *testing.T) { assert.Len(t, seatBids[0].FledgeAuctionConfigs, len(tc.expectedFledge)) assert.ElementsMatch(t, seatBids[0].FledgeAuctionConfigs, tc.expectedFledge) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } } @@ -1815,7 +1857,7 @@ func TestErrorReporting(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + bids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1825,6 +1867,7 @@ func TestErrorReporting(t *testing.T) { if errs[0].Error() != "Invalid params on BidRequest." { t.Errorf(`Error message was mutated. Expected "%s", Got "%s"`, "Invalid params on BidRequest.", errs[0].Error()) } + assert.True(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestSetAssetTypes(t *testing.T) { @@ -2049,7 +2092,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + _, _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -2105,9 +2148,10 @@ func TestCallRecordDNSTime(t *testing.T) { Client: &http.Client{Transport: DNSDoneTripper{}}, me: metricsMock, } + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} // Run test - bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now()) + bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now(), tmaxAdjustments) // Tried one or another, none seem to work without panicking metricsMock.AssertExpectations(t) @@ -2128,9 +2172,10 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) { Client: &http.Client{Transport: TLSHandshakeTripper{}}, me: metricsMock, } + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} // Run test - bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now()) + bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now(), tmaxAdjustments) // Tried one or another, none seem to work without panicking metricsMock.AssertExpectations(t) @@ -2217,8 +2262,8 @@ func TestTimeoutNotificationOn(t *testing.T) { logger := func(msg string, args ...interface{}) { loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...)) } - - bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, time.Now()) + tmaxAdjustments := &TmaxAdjustmentsPreprocessed{} + bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, time.Now(), tmaxAdjustments) // Wait a little longer than the 205ms mock server sleep. time.Sleep(210 * time.Millisecond) @@ -2296,7 +2341,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) { addCallSignHeader: true, bidAdjustments: bidAdjustments, } - _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + _, _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Empty(t, errs, "no errors should be returned") } @@ -2340,7 +2385,7 @@ func (bidder *goodSingleBidderWithStoredBidResp) MakeRequests(request *openrtb2. func (bidder *goodSingleBidderWithStoredBidResp) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.UnmarshalValid(response.Body, &bidResp); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) @@ -2475,7 +2520,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2487,7 +2531,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2510,7 +2553,7 @@ func TestExtraBid(t *testing.T) { bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ @@ -2528,6 +2571,7 @@ func TestExtraBid(t *testing.T) { return len(seatBids[i].Seat) < len(seatBids[j].Seat) }) assert.Equal(t, wantSeatBids, seatBids) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { @@ -2584,7 +2628,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm-allowed", Currency: "USD", @@ -2596,7 +2639,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2624,7 +2666,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ @@ -2639,6 +2681,7 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { assert.Equal(t, wantErrs, errs) assert.Len(t, seatBids, 2) assert.ElementsMatch(t, wantSeatBids, seatBids) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestExtraBidWithBidAdjustments(t *testing.T) { @@ -2666,7 +2709,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, - Seat: "pubmatic", + Seat: "PUBMATIC", }, { Bid: &openrtb2.Bid{ @@ -2693,7 +2736,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2709,9 +2751,8 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, - Seat: string(openrtb_ext.BidderPubmatic), + Seat: "PUBMATIC", Currency: "USD", }, } @@ -2721,10 +2762,10 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { bidderReq := BidderRequest{ BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}}, - BidderName: openrtb_ext.BidderPubmatic, + BidderName: "PUBMATIC", } bidAdjustments := map[string]float64{ - string(openrtb_ext.BidderPubmatic): 2, + string(openrtb_ext.BidderPubmatic): 2, // All lowercase value in bid adjustments to simulate it being case insensitive "groupm": 3, } @@ -2735,11 +2776,11 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - string(openrtb_ext.BidderPubmatic): { + "PUBMATIC": { Enabled: true, AllowedBidderCodes: []string{"groupm"}, }, @@ -2753,6 +2794,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { return len(seatBids[i].Seat) < len(seatBids[j].Seat) }) assert.Equal(t, wantSeatBids, seatBids) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { @@ -2807,7 +2849,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2823,7 +2864,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2848,7 +2888,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ @@ -2866,6 +2906,7 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { return len(seatBids[i].Seat) < len(seatBids[j].Seat) }) assert.Equal(t, wantSeatBids, seatBids) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestExtraBidWithMultiCurrencies(t *testing.T) { @@ -2920,7 +2961,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "INR", @@ -2936,7 +2976,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCPM: 3, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", @@ -2973,7 +3012,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, + seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{ Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ @@ -2991,6 +3030,7 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { return len(seatBids[i].Seat) < len(seatBids[j].Seat) }) assert.Equal(t, wantSeatBids, seatBids) + assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) } func TestGetBidType(t *testing.T) { @@ -3056,3 +3096,254 @@ func TestGetBidType(t *testing.T) { }) } } + +type mockBidderTmaxCtx struct { + startTime, deadline, now time.Time + ok bool +} + +func (m *mockBidderTmaxCtx) Deadline() (deadline time.Time, _ bool) { + return m.deadline, m.ok +} +func (m *mockBidderTmaxCtx) RemainingDurationMS(deadline time.Time) int64 { + return deadline.Sub(m.startTime).Milliseconds() +} + +func (m *mockBidderTmaxCtx) Until(t time.Time) time.Duration { + return t.Sub(m.now) +} + +func TestUpdateBidderTmax(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + var requestTmax int64 = 700 + + bidderReq := BidderRequest{ + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}, TMax: requestTmax}, + BidderName: "test", + } + extraInfo := &adapters.ExtraRequestInfo{} + + tests := []struct { + description string + requestTmax int64 + tmaxAdjustments *TmaxAdjustmentsPreprocessed + assertFn func(actualTmax int64) bool + }{ + { + description: "tmax-is-not-enabled", + requestTmax: requestTmax, + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: false}, + assertFn: func(actualTmax int64) bool { + return requestTmax == actualTmax + }, + }, + { + description: "updates-bidder-tmax", + requestTmax: requestTmax, + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 50, PBSResponsePreparationDuration: 50}, + assertFn: func(actualTmax int64) bool { + return requestTmax > actualTmax + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + bidResponse: &adapters.BidderResponse{}, + } + + now := time.Now() + ctx, cancel := context.WithDeadline(context.Background(), now.Add(500*time.Millisecond)) + defer cancel() + bidReqOptions := bidRequestOptions{bidderRequestStartTime: now, tmaxAdjustments: test.tmaxAdjustments} + bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "") + _, _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + assert.Empty(t, errs) + assert.True(t, test.assertFn(bidderImpl.bidRequest.TMax)) + }) + } +} + +func TestHasShorterDurationThanTmax(t *testing.T) { + var requestTmaxMS int64 = 700 + requestTmaxNS := requestTmaxMS * int64(time.Millisecond) + startTime := time.Date(2023, 5, 30, 1, 0, 0, 0, time.UTC) + now := time.Date(2023, 5, 30, 1, 0, 0, int(200*time.Millisecond), time.UTC) + deadline := time.Date(2023, 5, 30, 1, 0, 0, int(requestTmaxNS), time.UTC) + ctx := &mockBidderTmaxCtx{startTime: startTime, deadline: deadline, now: now, ok: true} + + tests := []struct { + description string + ctx bidderTmaxContext + requestTmax int64 + tmaxAdjustments TmaxAdjustmentsPreprocessed + expected bool + }{ + { + description: "tmax-disabled", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: false}, + expected: false, + }, + { + description: "remaing-duration-greater-than-bidder-response-min", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 50, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 40}, + expected: false, + }, + { + description: "remaing-duration-less-than-bidder-response-min", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 500}, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + assert.Equal(t, test.expected, hasShorterDurationThanTmax(test.ctx, test.tmaxAdjustments)) + }) + } +} + +func TestDoRequestImplWithTmax(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + requestStartTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + + bidRequest := adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`), + } + + bidderAdapter := bidderAdapter{ + me: &metricsConfig.NilMetricsEngine{}, + Client: server.Client(), + } + logger := func(msg string, args ...interface{}) {} + + tests := []struct { + ctxDeadline time.Time + description string + tmaxAdjustments *TmaxAdjustmentsPreprocessed + assertFn func(err error) + }{ + { + ctxDeadline: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + description: "returns-tmax-timeout-error", + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000}, + assertFn: func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) }, + }, + { + ctxDeadline: time.Now().Add(5 * time.Second), + description: "remaining-duration-greater-than-tmax-min", + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 100}, + assertFn: func(err error) { assert.Nil(t, err) }, + }, + { + description: "tmax-disabled", + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: false}, + assertFn: func(err error) { assert.Nil(t, err) }, + }, + { + description: "tmax-BidderResponseDurationMin-not-set", + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 0}, + assertFn: func(err error) { assert.Nil(t, err) }, + }, + { + description: "tmax-is-nil", + tmaxAdjustments: nil, + assertFn: func(err error) { assert.Nil(t, err) }, + }, + } + for _, test := range tests { + var ( + ctx context.Context + cancelFn context.CancelFunc + ) + + if test.ctxDeadline.IsZero() { + ctx = context.Background() + } else { + ctx, cancelFn = context.WithDeadline(context.Background(), test.ctxDeadline) + defer cancelFn() + } + + httpCallInfo := bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, requestStartTime, test.tmaxAdjustments) + test.assertFn(httpCallInfo.err) + } +} + +func TestDoRequestImplWithTmaxTimeout(t *testing.T) { + respStatus := 200 + respBody := "{\"bid\":false}" + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + requestStartTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + + bidRequest := adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`), + } + + metricsMock := &metrics.MetricsEngineMock{} + metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once() + metricsMock.On("RecordTMaxTimeout").Once() + + bidderAdapter := bidderAdapter{ + me: metricsMock, + Client: server.Client(), + } + logger := func(msg string, args ...interface{}) {} + + tests := []struct { + ctxDeadline time.Time + description string + tmaxAdjustments *TmaxAdjustmentsPreprocessed + assertFn func(err error) + }{ + { + ctxDeadline: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + description: "returns-tmax-timeout-error", + tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000}, + assertFn: func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) }, + }, + } + for _, test := range tests { + var ( + ctx context.Context + cancelFn context.CancelFunc + ) + + if test.ctxDeadline.IsZero() { + ctx = context.Background() + } else { + ctx, cancelFn = context.WithDeadline(context.Background(), test.ctxDeadline) + defer cancelFn() + } + + httpCallInfo := bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, requestStartTime, test.tmaxAdjustments) + test.assertFn(httpCallInfo.err) + } +} diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 8283a674dac..651b2c0f420 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -7,12 +7,12 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" goCurrency "golang.org/x/text/currency" ) @@ -31,14 +31,14 @@ type validatedBidder struct { bidder AdaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) { - seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) +func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { + seatBids, extraBidderRespInfo, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) for _, seatBid := range seatBids { if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) } } - return seatBids, errs + return seatBids, extraBidderRespInfo, errs } // validateBids will run some validation checks on the returned bids and excise any invalid bids diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index e7340e7d89b..a495556f424 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -65,7 +65,7 @@ func TestAllValidBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 4) assert.Len(t, errs, 0) @@ -135,7 +135,7 @@ func TestAllBadBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 0) assert.Len(t, errs, 7) @@ -216,7 +216,7 @@ func TestMixedBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 3) assert.Len(t, errs, 5) @@ -345,7 +345,7 @@ func TestCurrencyBids(t *testing.T) { addCallSignHeader: false, bidAdjustments: bidAdjustments, } - seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) + seatBids, _, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, expectedValidBids) assert.Len(t, errs, expectedErrs) @@ -354,9 +354,10 @@ func TestCurrencyBids(t *testing.T) { type mockAdaptedBidder struct { bidResponse []*entities.PbsOrtbSeatBid + extraRespInfo extraBidderRespInfo errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) { - return b.bidResponse, b.errorResponse +func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { + return b.bidResponse, b.extraRespInfo, b.errorResponse } diff --git a/exchange/entities/entities.go b/exchange/entities/entities.go index 1220da5c812..f106003d8ca 100644 --- a/exchange/entities/entities.go +++ b/exchange/entities/entities.go @@ -2,7 +2,7 @@ package entities import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // PbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder. diff --git a/exchange/events.go b/exchange/events.go index fd52d6b676a..b20eea328f6 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -1,16 +1,16 @@ package exchange import ( - "encoding/json" "time" - "github.com/prebid/prebid-server/exchange/entities" + "github.com/prebid/prebid-server/v2/exchange/entities" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/analytics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/endpoints/events" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // eventTracking has configuration fields needed for adding event tracking to an auction response @@ -28,7 +28,7 @@ type eventTracking struct { func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos config.BidderInfos, externalURL string) *eventTracking { return &eventTracking{ accountID: account.ID, - enabledForAccount: account.Events.IsEnabled(), + enabledForAccount: account.Events.Enabled, enabledForRequest: requestExtPrebid != nil && requestExtPrebid.Events != nil, auctionTimestampMs: ts.UnixNano() / 1e+6, integrationType: getIntegrationType(requestExtPrebid), @@ -91,7 +91,7 @@ func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName o winEventURL = ev.makeEventURL(analytics.Win, pbsBid, bidderName) } // wurl attribute is not in the schema, so we have to patch - patch, err := json.Marshal(map[string]string{"wurl": winEventURL}) + patch, err := jsonutil.Marshal(map[string]string{"wurl": winEventURL}) if err != nil { return jsonBytes, err } @@ -130,7 +130,7 @@ func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *entiti }) } -// isEnabled checks if events are enabled by default or on account/request level +// isEventAllowed checks if events are enabled by default or on account/request level func (ev *eventTracking) isEventAllowed() bool { return ev.enabledForAccount || ev.enabledForRequest } diff --git a/exchange/events_test.go b/exchange/events_test.go index 24dedf1a6f1..3d191f5f63c 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/exchange.go b/exchange/exchange.go index 24ca193f989..446dfaf9fdf 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,26 +14,29 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adservertargeting" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/floors" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v2/privacy" + + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adservertargeting" + "github.com/prebid/prebid-server/v2/bidadjustment" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/floors" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" "github.com/buger/jsonparser" "github.com/gofrs/uuid" @@ -78,7 +81,8 @@ type exchange struct { bidValidationEnforcement config.Validations requestSplitter requestSplitter macroReplacer macros.Replacer - floor config.PriceFloors + priceFloorEnabled bool + priceFloorFetcher floors.FloorFetcher } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -88,15 +92,15 @@ type seatResponseExtra struct { Warnings []openrtb_ext.ExtBidderMessage // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. - HttpCalls []*openrtb_ext.ExtHttpCall - MakeBidsTimeInfo adapters.MakeBidsTimeInfo + HttpCalls []*openrtb_ext.ExtHttpCall } type bidResponseWrapper struct { - adapterSeatBids []*entities.PbsOrtbSeatBid - adapterExtra *seatResponseExtra - bidder openrtb_ext.BidderName - adapter openrtb_ext.BidderName + adapterSeatBids []*entities.PbsOrtbSeatBid + adapterExtra *seatResponseExtra + bidder openrtb_ext.BidderName + adapter openrtb_ext.BidderName + bidderResponseStartTime time.Time } type BidIDGenerator interface { @@ -127,7 +131,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { return rand.Intn(100) < 50 } -func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer) Exchange { +func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer, priceFloorFetcher floors.FloorFetcher) Exchange { bidderToSyncerKey := map[string]string{} for bidder, syncer := range syncersByBidder { bidderToSyncerKey[bidder] = syncer.Key() @@ -172,7 +176,8 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid bidValidationEnforcement: cfg.Validations, requestSplitter: requestSplitter, macroReplacer: macroReplacer, - floor: cfg.PriceFloors, + priceFloorEnabled: cfg.PriceFloors.Enabled, + priceFloorFetcher: priceFloorFetcher, } } @@ -195,6 +200,7 @@ type AuctionRequest struct { GlobalPrivacyControlHeader string ImpExtInfoMap map[string]ImpExtInfo TCF2Config gdpr.TCF2ConfigReader + Activities privacy.ActivityControl // LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -203,13 +209,13 @@ type AuctionRequest struct { // map of imp id to stored response StoredAuctionResponses stored_responses.ImpsWithBidResponses // map of imp id to bidder to stored response - StoredBidResponses stored_responses.ImpBidderStoredResp - BidderImpReplaceImpID stored_responses.BidderImpReplaceImpID - PubID string - HookExecutor hookexecution.StageExecutor - QueryParams url.Values - // map of bidder to store duration needed for the MakeBids() calls and start time after MakeBids() calls - MakeBidsTimeInfo map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo + StoredBidResponses stored_responses.ImpBidderStoredResp + BidderImpReplaceImpID stored_responses.BidderImpReplaceImpID + PubID string + HookExecutor hookexecution.StageExecutor + QueryParams url.Values + BidderResponseStartTime time.Time + TmaxAdjustments *TmaxAdjustmentsPreprocessed } // BidderRequest holds the bidder specific request and all other @@ -220,6 +226,7 @@ type BidderRequest struct { BidderCoreName openrtb_ext.BidderName BidderLabels metrics.AdapterLabels BidderStoredResponses map[string]json.RawMessage + IsRequestAlias bool ImpReplaceImpId map[string]bool } @@ -228,7 +235,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog return nil, nil } - var floorErrs []error err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper) if err != nil { return nil, err @@ -261,10 +267,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } // Get currency rates conversions for the auction - conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions) + conversions := currency.GetAuctionCurrencyRates(e.currencyConverter, requestExtPrebid.CurrencyConversions) - if e.floor.Enabled { - floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions) + var floorErrs []error + if e.priceFloorEnabled { + floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher) } responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog) @@ -274,7 +281,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if err := r.BidRequestWrapper.RebuildRequest(); err != nil { return nil, err } - resolvedBidReq, err := json.Marshal(r.BidRequestWrapper.BidRequest) + resolvedBidReq, err := jsonutil.Marshal(r.BidRequestWrapper.BidRequest) if err != nil { return nil, err } @@ -316,7 +323,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog Prebid: *requestExtPrebid, SChain: requestExt.GetSChain(), } - bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue) + bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue, bidAdjustmentFactors) errs = append(errs, floorErrs...) mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) @@ -366,8 +373,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } else if r.Account.AlternateBidderCodes != nil { alternateBidderCodes = *r.Account.AlternateBidderCodes } - adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules) - r.MakeBidsTimeInfo = buildMakeBidsTimeInfoMap(adapterExtra) + var extraRespInfo extraAuctionResponseInfo + adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.TmaxAdjustments) + fledge = extraRespInfo.fledge + anyBidsReturned = extraRespInfo.bidsFound + r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime } var ( @@ -378,7 +388,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog ) if anyBidsReturned { - if e.floor.Enabled { + if e.priceFloorEnabled { var rejectedBids []*entities.PbsOrtbSeatBid var enforceErrs []error @@ -435,7 +445,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs) if debugLog.DebugEnabledOrOverridden { - if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + if bidRespExtBytes, err := jsonutil.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { debugLog.Data.Response = "Unable to marshal response ext for debugging" @@ -456,7 +466,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog if debugLog.DebugEnabledOrOverridden { - if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { + if bidRespExtBytes, err := jsonutil.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { debugLog.Data.Response = "Unable to marshal response ext for debugging" @@ -484,17 +494,14 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog e.bidValidationEnforcement.SetBannerCreativeMaxSize(r.Account.Validations) // Build the response - bidResponse, err := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs) - + bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs) bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute) bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt) - - bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBids) - if err != nil { return nil, err } + bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBids) return &AuctionResponse{ BidResponse: bidResponse, @@ -502,14 +509,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog }, nil } -func buildMakeBidsTimeInfoMap(adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra) map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo { - makeBidsInfoMap := make(map[openrtb_ext.BidderName]adapters.MakeBidsTimeInfo) - for bidderName, responseExtra := range adapterExtra { - makeBidsInfoMap[bidderName] = responseExtra.MakeBidsTimeInfo - } - return makeBidsInfoMap -} - func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_ext.ExtMultiBid { if prebid == nil || prebid.MultiBid == nil { return nil @@ -519,10 +518,14 @@ func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_e multiBidMap := make(map[string]openrtb_ext.ExtMultiBid) for _, multiBid := range prebid.MultiBid { if multiBid.Bidder != "" { - multiBidMap[multiBid.Bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(multiBid.Bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } else { for _, bidder := range multiBid.Bidders { - multiBidMap[bidder] = *multiBid + if bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder); bidderFound { + multiBidMap[string(bidderNormalized)] = *multiBid + } } } } @@ -574,15 +577,18 @@ func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory for impID, topBidsPerImp := range auc.winningBidsByBidder { impDeal := impDealMap[impID] for bidder, topBidsPerBidder := range topBidsPerImp { - maxBid := bidsToUpdate(multiBid, bidder.String()) - + bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder.String()) + if !bidderFound { + continue + } + maxBid := bidsToUpdate(multiBid, bidderNormalized.String()) for i, topBid := range topBidsPerBidder { if i == maxBid { break } if topBid.DealPriority > 0 { - if validateDealTier(impDeal[bidder]) { - updateHbPbCatDur(topBid, impDeal[bidder], bidCategory) + if validateDealTier(impDeal[bidderNormalized]) { + updateHbPbCatDur(topBid, impDeal[bidderNormalized], bidCategory) } else { errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", string(bidder), impID)) } @@ -664,16 +670,16 @@ func (e *exchange) getAllBids( experiment *openrtb_ext.Experiment, hookExecutor hookexecution.StageExecutor, pbsRequestStartTime time.Time, - bidAdjustmentRules map[string][]openrtb_ext.Adjustment) ( + bidAdjustmentRules map[string][]openrtb_ext.Adjustment, + tmaxAdjustments *TmaxAdjustmentsPreprocessed) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, - *openrtb_ext.Fledge, - bool) { + extraAuctionResponseInfo) { // Set up pointers to the bid results adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, len(bidderRequests)) adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests)) chBids := make(chan *bidResponseWrapper, len(bidderRequests)) - bidsFound := false + extraRespInfo := extraAuctionResponseInfo{} e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime)) @@ -697,15 +703,17 @@ func (e *exchange) getAllBids( reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - reqInfo.BidderRequestStartTime = start bidReqOptions := bidRequestOptions{ - accountDebugAllowed: accountDebugAllowed, - headerDebugAllowed: headerDebugAllowed, - addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), - bidAdjustments: bidAdjustments, + accountDebugAllowed: accountDebugAllowed, + headerDebugAllowed: headerDebugAllowed, + addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]), + bidAdjustments: bidAdjustments, + tmaxAdjustments: tmaxAdjustments, + bidderRequestStartTime: start, } - seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules) + seatBids, extraBidderRespInfo, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules) + brw.bidderResponseStartTime = extraBidderRespInfo.respProcessingStartTime // Add in time reporting elapsed := time.Since(start) @@ -716,11 +724,6 @@ func (e *exchange) getAllBids( if len(seatBids) != 0 { ae.HttpCalls = seatBids[0].HttpCalls } - // SeatBidsPreparationStartTime is needed to calculate duration for openrtb response preparation time metric - // No metric needs to be logged for requests which error out - if err == nil { - ae.MakeBidsTimeInfo = reqInfo.MakeBidsTimeInfo - } // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, elapsed) bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids) @@ -742,12 +745,13 @@ func (e *exchange) getAllBids( }, chBids) go bidderRunner(bidder, conversions) } - var fledge *openrtb_ext.Fledge // Wait for the bidders to do their thing for i := 0; i < len(bidderRequests); i++ { brw := <-chBids - + if !brw.bidderResponseStartTime.IsZero() { + extraRespInfo.bidderResponseStartTime = brw.bidderResponseStartTime + } //if bidder returned no bids back - remove bidder from further processing for _, seatBid := range brw.adapterSeatBids { if seatBid != nil { @@ -758,20 +762,17 @@ func (e *exchange) getAllBids( } else { adapterBids[bidderName] = seatBid } + extraRespInfo.bidsFound = true } // collect fledgeAuctionConfigs separately from bids, as empty seatBids may be discarded - fledge = collectFledgeFromSeatBid(fledge, bidderName, brw.adapter, seatBid) + extraRespInfo.fledge = collectFledgeFromSeatBid(extraRespInfo.fledge, bidderName, brw.adapter, seatBid) } } //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra - - if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].Bids) > 0 { - bidsFound = true - } } - return adapterBids, adapterExtra, fledge, bidsFound + return adapterBids, adapterExtra, extraRespInfo } func collectFledgeFromSeatBid(fledge *openrtb_ext.Fledge, bidderName openrtb_ext.BidderName, adapterName openrtb_ext.BidderName, seatBid *entities.PbsOrtbSeatBid) *openrtb_ext.Fledge { @@ -851,6 +852,8 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { ret[metrics.AdapterErrorFailedToRequestBids] = s case errortypes.AlternateBidderCodeWarningCode: ret[metrics.AdapterErrorValidation] = s + case errortypes.TmaxTimeoutErrorCode: + ret[metrics.AdapterErrorTmaxTimeout] = s default: ret[metrics.AdapterErrorUnknown] = s } @@ -884,9 +887,8 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error) (*openrtb2.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error) *openrtb2.BidResponse { bidResponse := new(openrtb2.BidResponse) - var err error bidResponse.ID = bidRequest.ID if len(liveAdapters) == 0 { @@ -907,7 +909,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ } bidResponse.SeatBid = seatBids - return bidResponse, err + return bidResponse } func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, error) { @@ -1275,7 +1277,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea } } - if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur); err != nil { + if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, adapter); err != nil { errs = append(errs, err) } else { result = append(result, *bid.Bid) @@ -1289,11 +1291,11 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea return result, errs } -func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string) (json.RawMessage, error) { +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string, adapter openrtb_ext.BidderName) (json.RawMessage, error) { var extMap map[string]interface{} if len(ext) != 0 { - if err := json.Unmarshal(ext, &extMap); err != nil { + if err := jsonutil.Unmarshal(ext, &extMap); err != nil { return nil, err } } else { @@ -1317,12 +1319,18 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx Meta openrtb_ext.ExtBidPrebidMeta `json:"meta"` } `json:"prebid"` }{} - if err := json.Unmarshal(ext, &metaContainer); err != nil { - return nil, fmt.Errorf("error validaing response from server, %s", err) + if err := jsonutil.Unmarshal(ext, &metaContainer); err != nil { + return nil, fmt.Errorf("error validating response from server, %s", err) } prebid.Meta = &metaContainer.Prebid.Meta } + if prebid.Meta == nil { + prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{} + } + + prebid.Meta.AdapterCode = adapter.String() + // ext.prebid.storedrequestattributes and ext.prebid.passthrough if impExtInfo, ok := impExtInfoMap[impId]; ok { prebid.Passthrough = impExtInfoMap[impId].Passthrough @@ -1338,7 +1346,7 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx } } extMap[openrtb_ext.PrebidExtKey] = prebid - return json.Marshal(extMap) + return jsonutil.Marshal(extMap) } // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, @@ -1354,34 +1362,6 @@ func (e *exchange) getBidCacheInfo(bid *entities.PbsOrtbBid, auction *auction) ( return } -func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { - if requestRates == nil { - // No bidRequest.ext.currency field was found, use PBS rates as usual - return e.currencyConverter.Rates() - } - - // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false - // only if it's explicitly set to false - usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates - - if !usePbsRates { - // At this point, we can safely assume the ConversionRates map is not empty because - // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have - // thrown an error under such conditions. - return currency.NewRates(requestRates.ConversionRates) - } - - // Both PBS and custom rates can be used, check if ConversionRates is not empty - if len(requestRates.ConversionRates) == 0 { - // Custom rates map is empty, use PBS rates only - return e.currencyConverter.Rates() - } - - // Return an AggregateConversions object that includes both custom and PBS currency rates but will - // prioritize custom rates over PBS rates whenever a currency rate is found in both - return currency.NewAggregateConversions(currency.NewRates(requestRates.ConversionRates), e.currencyConverter.Rates()) -} - func findCacheID(bid *entities.PbsOrtbBid, auction *auction) (string, bool) { if bid != nil && bid.Bid != nil && auction != nil { if id, found := auction.cacheIds[bid.Bid]; found { @@ -1442,7 +1422,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag for impId, storedResp := range storedAuctionResponses { var seatBids []openrtb2.SeatBid - if err := json.Unmarshal(storedResp, &seatBids); err != nil { + if err := jsonutil.UnmarshalValid(storedResp, &seatBids); err != nil { return nil, nil, nil, err } for _, seat := range seatBids { @@ -1461,7 +1441,7 @@ func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessag if seat.Ext != nil { var seatExt openrtb_ext.ExtBidResponse - if err := json.Unmarshal(seat.Ext, &seatExt); err != nil { + if err := jsonutil.Unmarshal(seat.Ext, &seatExt); err != nil { return nil, nil, nil, err } // add in FLEDGE response with impId substituted diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 946a8587354..aa354ea22f6 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math" "net/http" "net/http/httptest" @@ -21,26 +20,28 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" jsonpatch "gopkg.in/evanphx/json-patch.v4" @@ -81,7 +82,7 @@ func TestNewExchange(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { if biddersInfo[string(bidderName)].IsEnabled() { @@ -131,7 +132,7 @@ func TestCharacterEscape(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -167,12 +168,9 @@ func TestCharacterEscape(t *testing.T) { var errList []error // 4) Build bid response - bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList) + bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList) // 5) Assert we have no errors and one '&' character as we are supposed to - if err != nil { - t.Errorf("exchange.buildBidResponse returned unexpected error: %v", err) - } if len(errList) > 0 { t.Errorf("exchange.buildBidResponse returned %d errors", len(errList)) } @@ -379,9 +377,9 @@ func TestDebugBehaviour(t *testing.T) { // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) assert.NotNilf(t, outBidResponse.Ext, "%s. outBidResponse.Ext should not be nil \n", test.desc) - + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) actualExt := &openrtb_ext.ExtBidResponse{} - err = json.Unmarshal(outBidResponse.Ext, actualExt) + err = jsonutil.UnmarshalValid(outBidResponse.Ext, actualExt) assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext) assert.NotEmpty(t, actualExt.Prebid, "%s. ext.prebid should not be empty") @@ -540,9 +538,10 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { // Assert no HoldAuction err assert.NoErrorf(t, err, "ex.HoldAuction returned an err") assert.NotNilf(t, outBidResponse.Ext, "outBidResponse.Ext should not be nil") + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) actualExt := &openrtb_ext.ExtBidResponse{} - err = json.Unmarshal(outBidResponse.Ext, actualExt) + err = jsonutil.UnmarshalValid(outBidResponse.Ext, actualExt) assert.NoErrorf(t, err, "JSON field unmarshaling err. ") assert.NotEmpty(t, actualExt.Prebid, "ext.prebid should not be empty") @@ -573,9 +572,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } func TestOverrideWithCustomCurrency(t *testing.T) { - - mockCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } mockCurrencyConverter := currency.NewRateConverter( mockCurrencyClient, @@ -715,6 +713,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { // Assertions assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) if test.expected.numBids > 0 { // Assert out currency @@ -740,11 +739,11 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } func TestAdapterCurrency(t *testing.T) { - fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } currencyConverter := currency.NewRateConverter( - fakeCurrencyClient, + mockCurrencyClient, "currency.fake.com", 24*time.Hour, ) @@ -769,7 +768,7 @@ func TestAdapterCurrency(t *testing.T) { categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""), + openrtb_ext.BidderName("appnexus"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("appnexus"), nil, ""), }, } e.requestSplitter = requestSplitter{ @@ -783,7 +782,7 @@ func TestAdapterCurrency(t *testing.T) { Imp: []openrtb2.Imp{{ ID: "some-impression-id", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"prebid":{"bidder":{"foo":{"placementId":1}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`), }}, Site: &openrtb2.Site{ Page: "prebid.org", @@ -805,7 +804,7 @@ func TestAdapterCurrency(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "some-request-id", response.ID, "Response ID") assert.Empty(t, response.SeatBid, "Response Bids") - assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") + assert.Contains(t, string(response.Ext), `"errors":{"appnexus":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext") // Test Currency Converter Properly Passed To Adapter if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { @@ -815,13 +814,20 @@ func TestAdapterCurrency(t *testing.T) { } } -func TestFloorsSignalling(t *testing.T) { +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} - fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`, +func TestFloorsSignalling(t *testing.T) { + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`, } currencyConverter := currency.NewRateConverter( - fakeCurrencyClient, + mockCurrencyClient, "currency.com", 24*time.Hour, ) @@ -839,7 +845,8 @@ func TestFloorsSignalling(t *testing.T) { currencyConverter: currencyConverter, categoriesFetcher: nilCategoryFetcher{}, bidIDGenerator: &mockBidIDGenerator{false, false}, - floor: config.PriceFloors{Enabled: true}, + priceFloorEnabled: true, + priceFloorFetcher: &mockPriceFloorFetcher{}, } e.requestSplitter = requestSplitter{ me: e.me, @@ -984,208 +991,6 @@ func TestFloorsSignalling(t *testing.T) { } } -func TestGetAuctionCurrencyRates(t *testing.T) { - - pbsRates := map[string]map[string]float64{ - "MXN": { - "USD": 20.13, - "EUR": 27.82, - "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates - }, - } - - customRates := map[string]map[string]float64{ - "MXN": { - "USD": 25.00, // different rate than in pbsRates - "EUR": 27.82, // same as in pbsRates - "GBP": 31.12, // not found in pbsRates at all - }, - } - - expectedRateEngineRates := map[string]map[string]float64{ - "MXN": { - "USD": 25.00, // rates engine will prioritize the value found in custom rates - "EUR": 27.82, // same value in both the engine reads the custom entry first - "JPY": 5.09, // the engine will find it in the pbsRates conversions - "GBP": 31.12, // the engine will find it in the custom conversions - }, - } - - boolTrue := true - boolFalse := false - - type testInput struct { - pbsRates map[string]map[string]float64 - bidExtCurrency *openrtb_ext.ExtRequestCurrency - } - type testOutput struct { - constantRates bool - resultingRates map[string]map[string]float64 - } - testCases := []struct { - desc string - given testInput - expected testOutput - }{ - { - "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - resultingRates: expectedRateEngineRates, - }, - }, - { - "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: nil, - }, - testOutput{ - resultingRates: pbsRates, - }, - }, - { - "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter", - testInput{ - pbsRates: nil, - bidExtCurrency: nil, - }, - testOutput{ - constantRates: true, - }, - }, - } - - for _, tc := range testCases { - - // Test setup: - jsonPbsRates, err := json.Marshal(tc.given.pbsRates) - if err != nil { - t.Fatalf("Failed to marshal PBS rates: %v", err) - } - - // Init mock currency conversion service - mockCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, - } - mockCurrencyConverter := currency.NewRateConverter( - mockCurrencyClient, - "currency.fake.com", - 24*time.Hour, - ) - mockCurrencyConverter.Run() - - e := new(exchange) - e.currencyConverter = mockCurrencyConverter - - // Run test - auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency) - - // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected - rate, err := auctionRates.GetRate("USD", "USD") - assert.NoError(t, err, tc.desc) - assert.Equal(t, float64(1), rate, tc.desc) - - // If we expect an error, assert we have one along with a conversion rate of zero - if tc.expected.constantRates { - rate, err := auctionRates.GetRate("USD", "MXN") - assert.Error(t, err, tc.desc) - assert.Equal(t, float64(0), rate, tc.desc) - } else { - for fromCurrency, rates := range tc.expected.resultingRates { - for toCurrency, expectedRate := range rates { - actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) - assert.NoError(t, err, tc.desc) - assert.Equal(t, expectedRate, actualRate, tc.desc) - } - } - } - } -} func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -1362,6 +1167,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { continue } else { assert.NoErrorf(t, err, "%s: %s. HoldAuction error: %v \n", testGroup.groupDesc, test.desc, err) + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) } // Assert returned bid @@ -1426,7 +1232,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }.Builder - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1530,10 +1336,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error // 4) Build bid response - bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList) - - // 5) Assert we have no errors and the bid response we expected - assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") + bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList) expectedBidResponse := &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ @@ -1629,7 +1432,7 @@ func TestBidReturnsCreative(t *testing.T) { assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description) var bidExt openrtb_ext.ExtBid - json.Unmarshal(resultingBids[0].Ext, &bidExt) + jsonutil.UnmarshalValid(resultingBids[0].Ext, &bidExt) assert.Equal(t, 0, bidExt.Prebid.DealPriority, "%s. Test should have DealPriority set to 0", test.description) assert.Equal(t, false, bidExt.Prebid.DealTierSatisfied, "%s. Test should have DealTierSatisfied set to false", test.description) } @@ -1786,7 +1589,7 @@ func TestBidResponseCurrency(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1828,7 +1631,7 @@ func TestBidResponseCurrency(t *testing.T) { Price: 9.517803, W: 300, H: 250, - Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"type":"banner"}}`), + Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"banner"}}`), }, }, }, @@ -1906,8 +1709,7 @@ func TestBidResponseCurrency(t *testing.T) { } // Run tests for i := range testCases { - actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList) - assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err)) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList) assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext)) } } @@ -1933,7 +1735,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { t.Fatalf("Error intializing adapters: %v", adaptersErr) } - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1969,12 +1771,11 @@ func TestBidResponseImpExtInfo(t *testing.T) { impExtInfo["some-impression-id"] = ImpExtInfo{ true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), - json.RawMessage(`{"imp_passthrough_val": 1}`)} + json.RawMessage(`{"imp_passthrough_val":1}`)} - expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` + expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` - actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList) - assert.NoError(t, err, fmt.Sprintf("imp ext info was not passed through correctly: %s", err)) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList) resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext) assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect") @@ -2026,7 +1827,7 @@ func TestRaceIntegration(t *testing.T) { }, }.Builder - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -2085,7 +1886,7 @@ func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: json.RawMessage(`{"ext_field":"value}"}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), }, { Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, @@ -2094,7 +1895,7 @@ func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest { W: 300, H: 600, }, - Ext: json.RawMessage(`{"ext_field":"value}"}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), }}, } } @@ -2124,7 +1925,7 @@ func TestPanicRecovery(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -2194,7 +1995,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { allowAllBidders: true, }, }.Builder - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -2263,8 +2064,13 @@ func TestTimeoutComputation(t *testing.T) { func TestExchangeJSON(t *testing.T) { if specFiles, err := os.ReadDir("./exchangetest"); err == nil { for _, specFile := range specFiles { + if !strings.HasSuffix(specFile.Name(), ".json") { + continue + } + fileName := "./exchangetest/" + specFile.Name() fileDisplayName := "exchange/exchangetest/" + specFile.Name() + t.Run(fileDisplayName, func(t *testing.T) { specData, err := loadFile(fileName) if assert.NoError(t, err, "Failed to load contents of file %s: %v", fileDisplayName, err) { @@ -2283,7 +2089,7 @@ func loadFile(filename string) (*exchangeSpec, error) { } var spec exchangeSpec - if err := json.Unmarshal(specData, &spec); err != nil { + if err := jsonutil.UnmarshalValid(specData, &spec); err != nil { return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err) } @@ -2356,12 +2162,14 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { impExtInfoMap[impID] = ImpExtInfo{} } + activityControl := privacy.NewActivityControl(spec.AccountPrivacy) + auctionRequest := &AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest}, Account: config.Account{ ID: "testaccount", Events: config.Events{ - Enabled: &spec.EventsEnabled, + Enabled: spec.EventsEnabled, }, DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled}, @@ -2371,13 +2179,14 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { ImpExtInfoMap: impExtInfoMap, HookExecutor: &hookexecution.EmptyHookExecutor{}, TCF2Config: gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, config.AccountGDPR{}), + Activities: activityControl, } if spec.MultiBid != nil { auctionRequest.Account.DefaultBidLimit = spec.MultiBid.AccountMaxBid requestExt := &openrtb_ext.ExtRequest{} - err := json.Unmarshal(spec.IncomingRequest.OrtbRequest.Ext, requestExt) + err := jsonutil.UnmarshalValid(spec.IncomingRequest.OrtbRequest.Ext, requestExt) assert.NoError(t, err, "invalid request ext") validatedMultiBids, errs := openrtb_ext.ValidateAndBuildExtMultiBid(&requestExt.Prebid) for _, err := range errs { // same as in validateRequestExt(). @@ -2388,7 +2197,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } requestExt.Prebid.MultiBid = validatedMultiBids - updateReqExt, err := json.Marshal(requestExt) + updateReqExt, err := jsonutil.Marshal(requestExt) assert.NoError(t, err, "invalid request ext") auctionRequest.BidRequestWrapper.Ext = updateReqExt } @@ -2451,7 +2260,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { actualPassthrough := "" actualBidRespExt := &openrtb_ext.ExtBidResponse{} if bid.Ext != nil { - if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, actualBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } if actualBidRespExt.Prebid != nil { @@ -2460,7 +2269,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } expectedBidRespExt := &openrtb_ext.ExtBidResponse{} if spec.Response.Ext != nil { - if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } if expectedBidRespExt.Prebid != nil { @@ -2491,11 +2300,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { actualBidRespExt := &openrtb_ext.ExtBidResponse{} expectedBidRespExt := &openrtb_ext.ExtBidResponse{} if bid.Ext != nil { - if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, actualBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } } - if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil { + if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } @@ -2525,7 +2334,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons return nil } else { responseTimes := make(map[string]int) - if err := json.Unmarshal(data, &responseTimes); err != nil { + if err := jsonutil.UnmarshalValid(data, &responseTimes); err != nil { t.Errorf("%s: Failed to unmarshal ext.responsetimemillis into map[string]int: %v", context, err) return nil } @@ -2629,7 +2438,8 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] server: config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter}, bidValidationEnforcement: hostBidValidation, requestSplitter: requestSplitter, - floor: config.PriceFloors{Enabled: floorsFlag}, + priceFloorEnabled: floorsFlag, + priceFloorFetcher: &mockPriceFloorFetcher{}, } } @@ -2960,83 +2770,80 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { } func TestCategoryDedupe(t *testing.T) { - categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - requestExt := newExtRequest() - targData := &targetData{ priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity, includeWinners: true, } - adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) + // bid3 and bid5 will be same price, category, and duration so one of them should be removed based on the dedupe generator + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: []string{"IAB1-4"}, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: []string{"IAB1-INVALID"}, W: 1, H: 1} + bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} - cats1 := []string{"IAB1-3"} - cats2 := []string{"IAB1-4"} - // bid3 will be same price, category, and duration as bid1 so one of them should get removed - cats4 := []string{"IAB1-2000"} - bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 10.0000, OriginalBidCur: "USD"} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, OriginalBidCPM: 15.0000, OriginalBidCur: "USD"} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} + bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} + bid1_5 := entities.PbsOrtbBid{Bid: &bid5, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 15.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bidderName1 := openrtb_ext.BidderName("appnexus") - selectedBids := make(map[string]int) - expectedCategories := map[string]string{ - "bid_id1": "10.00_Electronics_30s", - "bid_id2": "14.00_Sports_50s", - "bid_id3": "20.00_Electronics_30s", - "bid_id5": "20.00_Electronics_30s", + tests := []struct { + name string + dedupeGeneratorValue bool + expectedBids []*entities.PbsOrtbBid + expectedCategories map[string]string + }{ + { + name: "bid_id5_selected_over_bid_id3", + dedupeGeneratorValue: true, + expectedBids: []*entities.PbsOrtbBid{&bid1_2, &bid1_5}, + expectedCategories: map[string]string{ + "bid_id2": "14.00_Sports_50s", + "bid_id5": "20.00_Electronics_30s", + }, + }, + { + name: "bid_id3_selected_over_bid_id5", + dedupeGeneratorValue: false, + expectedBids: []*entities.PbsOrtbBid{&bid1_2, &bid1_3}, + expectedCategories: map[string]string{ + "bid_id2": "14.00_Sports_50s", + "bid_id3": "20.00_Electronics_30s", + }, + }, } - numIterations := 10 - - // Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes - // and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but - // if you notice false fails from this test increase numIterations to make it even less likely to happen. - for i := 0; i < numIterations; i++ { - innerBids := []*entities.PbsOrtbBid{ - &bid1_1, - &bid1_2, - &bid1_3, - &bid1_4, - &bid1_5, - } - - seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"} - bidderName1 := openrtb_ext.BidderName("appnexus") - - adapterBids[bidderName1] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) - - assert.Equal(t, nil, err, "Category mapping error should be empty") - assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") - assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") - assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected") - assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") - assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + bidderName1: { + Bids: []*entities.PbsOrtbBid{ + &bid1_1, + &bid1_2, + &bid1_3, + &bid1_4, + &bid1_5, + }, + Currency: "USD", + }, + } + deduplicateGenerator := fakeRandomDeduplicateBidBooleanGenerator{returnValue: tt.dedupeGeneratorValue} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &nonBids{}) - for bidId, bidCat := range bidCategory { - assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match") - selectedBids[bidId]++ - } + assert.Nil(t, err) + assert.Equal(t, 3, len(rejections)) + assert.Equal(t, adapterBids[bidderName1].Bids, tt.expectedBids) + assert.Equal(t, bidCategory, tt.expectedCategories) + }) } - - assert.Equal(t, numIterations, selectedBids["bid_id2"], "Bid 2 did not make it through every time") - assert.Equal(t, 0, selectedBids["bid_id1"], "Bid 1 should be rejected on every iteration due to lower price") - assert.NotEqual(t, 0, selectedBids["bid_id3"], "Bid 3 should be accepted at least once") - assert.NotEqual(t, 0, selectedBids["bid_id5"], "Bid 5 should be accepted at least once") } func TestNoCategoryDedupe(t *testing.T) { @@ -3580,92 +3387,168 @@ func TestUpdateRejections(t *testing.T) { } func TestApplyDealSupport(t *testing.T) { + type testInput struct { + dealPriority int + impExt json.RawMessage + targ map[string]string + bidderName openrtb_ext.BidderName + } + + type testOutput struct { + hbPbCatDur string + dealErr string + dealTierSatisfied bool + } + testCases := []struct { - description string - dealPriority int - impExt json.RawMessage - targ map[string]string - expectedHbPbCatDur string - expectedDealErr string - expectedDealTierSatisfied bool + description string + in testInput + expected testOutput }{ { - description: "hb_pb_cat_dur should be modified", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_movies_30s", + description: "hb_pb_cat_dur should be modified", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "tier5_movies_30s", - expectedDealErr: "", - expectedDealTierSatisfied: true, }, { - description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", - dealPriority: 9, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_medicine_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the impExt", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"APPnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_medicine_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to invalid config", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_games_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("APPnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_games_30s", - expectedDealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to deal priority of 0", - dealPriority: 0, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_auto_30s", + description: "hb_pb_cat_dur should not be modified due to unknown bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("unknown"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to invalid config", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_games_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_games_30s", + dealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to deal priority of 0", + in: testInput{ + dealPriority: 0, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_auto_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_auto_30s", + dealErr: "", + dealTierSatisfied: false, }, - expectedHbPbCatDur: "12.00_auto_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, }, } - bidderName := openrtb_ext.BidderName("appnexus") for _, test := range testCases { bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", Imp: []openrtb2.Imp{ { ID: "imp_id1", - Ext: test.impExt, + Ext: test.in.impExt, }, }, } - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.in.dealPriority, false, "", 0, "USD", ""} bidCategory := map[string]string{ - bid.Bid.ID: test.targ["hb_pb_cat_dur"], + bid.Bid.ID: test.in.targ["hb_pb_cat_dur"], } auc := &auction{ winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { - bidderName: {&bid}, + test.in.bidderName: {&bid}, }, }, } dealErrs := applyDealSupport(bidRequest, auc, bidCategory, nil) - assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName][0].Bid.ID], test.description) - assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName][0].DealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description) - if len(test.expectedDealErr) > 0 { - assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors") + assert.Equal(t, test.expected.hbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][test.in.bidderName][0].Bid.ID], test.description) + assert.Equal(t, test.expected.dealTierSatisfied, auc.winningBidsByBidder["imp_id1"][test.in.bidderName][0].DealTierSatisfied, "expected.dealTierSatisfied=%v when %v", test.expected.dealTierSatisfied, test.description) + if len(test.expected.dealErr) > 0 { + assert.Containsf(t, dealErrs, errors.New(test.expected.dealErr), "Expected error message not found in deal errors") } } } @@ -4029,7 +3912,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4039,7 +3922,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), nil}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4048,7 +3931,7 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 0, - expectedBidExt: `{"origbidcpm": 0,"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, expectedErrMessage: "", }, { @@ -4058,7 +3941,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4068,7 +3951,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4078,7 +3961,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: nil, - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4088,7 +3971,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4098,7 +3981,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4108,7 +3991,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4118,7 +4001,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{}, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4128,7 +4011,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4138,7 +4021,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4148,7 +4031,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4158,7 +4041,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: -1, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode":"adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4168,7 +4051,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner","meta":{"adaptercode":"adapter"}}, "origbidcur": "USD"}`, expectedErrMessage: "", }, //Error cases @@ -4176,37 +4059,30 @@ func TestMakeBidExtJSON(t *testing.T) { description: "Invalid extension, valid extBidPrebid and valid imp ext info", ext: json.RawMessage(`{invalid json}`), extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`"prebid": {"passthrough": {"imp_passthrough_val": some_val}}"`)}}, expectedBidExt: ``, - expectedErrMessage: "invalid character", - }, - { - description: "Valid extension, empty extBidPrebid and invalid imp ext info", - ext: json.RawMessage(`{"video":{"h":100}}`), - extBidPrebid: openrtb_ext.ExtBidPrebid{}, - impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`), nil}}, - expectedBidExt: ``, - expectedErrMessage: "invalid character", + expectedErrMessage: "expects \" or n, but found i", }, { description: "Meta - Invalid", ext: json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, impExtInfo: nil, - expectedErrMessage: "error validaing response from server, json: cannot unmarshal string into Go struct field ExtBidPrebidMeta.prebid.meta.brandId of type int", + expectedErrMessage: "error validating response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", }, - // add invalid } for _, test := range testCases { - result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur) + t.Run(test.description, func(t *testing.T) { + var adapter openrtb_ext.BidderName = "adapter" + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, adapter) - if test.expectedErrMessage == "" { - assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") - assert.NoError(t, err, "Error should not be returned") - } else { - assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") - } + if test.expectedErrMessage == "" { + assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") + assert.NoError(t, err, "Error should not be returned") + } else { + assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") + } + }) } } @@ -4242,7 +4118,7 @@ func TestStoredAuctionResponses(t *testing.T) { SeatBid: []openrtb2.SeatBid{ { Bid: []openrtb2.Bid{ - {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"type":"video"}}`)}, + {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video"}}`)}, }, Seat: "appnexus", }, @@ -4663,7 +4539,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // Define mock incoming bid requeset mockBidRequest := &openrtb2.BidRequest{ @@ -5143,6 +5019,7 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) { // Assertions assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) assert.NotNil(t, outBidResponse) + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) // So 2 seatBids are expected as, // the default "" and "pubmatic" bids will be in one seat and the extra-bids "groupm"/"appnexus"/"ix" in another seat. @@ -5160,6 +5037,325 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) { } } +func TestGetAllBids(t *testing.T) { + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + type testIn struct { + bidderRequests []BidderRequest + bidAdjustments map[string]float64 + conversions currency.Conversions + accountDebugAllowed bool + globalPrivacyControlHeader string + headerDebugAllowed bool + alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes + experiment *openrtb_ext.Experiment + hookExecutor hookexecution.StageExecutor + pbsRequestStartTime time.Time + bidAdjustmentRules map[string][]openrtb_ext.Adjustment + tmaxAdjustments *TmaxAdjustmentsPreprocessed + adapterMap map[openrtb_ext.BidderName]AdaptedBidder + } + type testResults struct { + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra + extraRespInfo extraAuctionResponseInfo + } + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "alternateBidderCodes-config-absent: pubmatic bidder returns bids with 'pubmatic' and 'groupm' seats", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "1"}, Seat: "pubmatic"}, + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{ + { + Code: errortypes.AlternateBidderCodeWarningCode, + Message: `alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`, + }, + }, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + }, + OriginalBidCur: "USD", + }, + }, + Currency: "USD", + Seat: "pubmatic", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "alternateBidderCodes-enabled: pubmatic bidder returns bids with 'pubmatic' and 'groupm' seats", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "1"}, Seat: "pubmatic"}, + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + }, + OriginalBidCur: "USD", + }, + }, + Currency: "USD", + Seat: "pubmatic", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + "groupm": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "2", + }, + OriginalBidCur: "USD", + }, + }, + Currency: "USD", + Seat: "groupm", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "alternateBidderCodes-enabled: pubmatic bidder returns bids with only 'groupm' seat", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "groupm": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "2", + }, + OriginalBidCur: "USD", + }, + }, + Currency: "USD", + Seat: "groupm", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "bidder responded with empty bid", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{}, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: false, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{}, + }, + }, + } + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + e := exchange{ + cache: &wellBehavedCache{}, + me: &metricsConf.NilMetricsEngine{}, + gdprPermsBuilder: fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder, + adapterMap: test.in.adapterMap, + } + + adapterBids, adapterExtra, extraRespInfo := e.getAllBids(context.Background(), test.in.bidderRequests, test.in.bidAdjustments, + test.in.conversions, test.in.accountDebugAllowed, test.in.globalPrivacyControlHeader, test.in.headerDebugAllowed, test.in.alternateBidderCodes, test.in.experiment, + test.in.hookExecutor, test.in.pbsRequestStartTime, test.in.bidAdjustmentRules, test.in.tmaxAdjustments) + + assert.Equalf(t, test.expected.extraRespInfo.bidsFound, extraRespInfo.bidsFound, "extraRespInfo.bidsFound mismatch") + assert.Equalf(t, test.expected.adapterBids, adapterBids, "adapterBids mismatch") + assert.Equalf(t, len(test.expected.adapterExtra), len(adapterExtra), "adapterExtra length mismatch") + for adapter, extra := range test.expected.adapterExtra { + assert.Equalf(t, extra.Warnings, adapterExtra[adapter].Warnings, "adapterExtra.Warnings mismatch for adapter [%s]", adapter) + } + }) + } +} + type MockSigner struct { data string } @@ -5191,6 +5387,7 @@ type exchangeSpec struct { FledgeEnabled bool `json:"fledge_enabled,omitempty"` MultiBid *multiBidSpec `json:"multiBid,omitempty"` Server exchangeServer `json:"server,omitempty"` + AccountPrivacy *config.AccountPrivacy `json:"accountPrivacy,omitempty"` } type multiBidSpec struct { @@ -5271,7 +5468,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBids []*entities.PbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBids []*entities.PbsOrtbSeatBid, extaInfo extraBidderRespInfo, errs []error) { if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok { if expectedRequest != nil { if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) { @@ -5337,12 +5534,12 @@ func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest B func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() - actualJSON, err := json.Marshal(actual) + actualJSON, err := jsonutil.Marshal(actual) if err != nil { t.Fatalf("%s failed to marshal actual BidRequest into JSON. %v", description, err) } - expectedJSON, err := json.Marshal(expected) + expectedJSON, err := jsonutil.Marshal(expected) if err != nil { t.Fatalf("%s failed to marshal expected BidRequest into JSON. %v", description, err) } @@ -5360,12 +5557,12 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidR // this implementation detail, I'm cutting a corner and ignoring it here. actualSeats := mapifySeatBids(t, description, actual.SeatBid) expectedSeats := mapifySeatBids(t, description, expected.SeatBid) - actualJSON, err := json.Marshal(actualSeats) + actualJSON, err := jsonutil.Marshal(actualSeats) if err != nil { t.Fatalf("%s failed to marshal actual BidResponse into JSON. %v", description, err) } - expectedJSON, err := json.Marshal(expectedSeats) + expectedJSON, err := jsonutil.Marshal(expectedSeats) if err != nil { t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err) } @@ -5437,7 +5634,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (posb []*entities.PbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (posb []*entities.PbsOrtbSeatBid, extraInfo extraBidderRespInfo, errs []error) { panic("Panic! Panic! The world is ending!") } @@ -5459,19 +5656,6 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } -// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body -type fakeCurrencyRatesHttpClient struct { - responseBody string -} - -func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(m.responseBody)), - }, nil -} - type mockBidder struct { mock.Mock lastExtraRequestInfo *adapters.ExtraRequestInfo @@ -5495,13 +5679,13 @@ func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, e impID := imp.ID var bidderExts map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { + if err := jsonutil.UnmarshalValid(imp.Ext, &bidderExts); err != nil { return nil, "", err } var extPrebid openrtb_ext.ExtImpPrebid if bidderExts[openrtb_ext.PrebidExtKey] != nil { - if err := json.Unmarshal(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil { + if err := jsonutil.UnmarshalValid(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil { return nil, "", err } } @@ -5569,6 +5753,7 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) { _, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) // Assert no HoldAuction err assert.NoErrorf(t, err, "ex.HoldAuction returned an err") + assert.False(t, auctionRequest.BidderResponseStartTime.IsZero()) // check stage outcomes assert.Equal(t, len(exec.GetOutcomes()), len(e.adapterMap), "stage outcomes append operation failed") @@ -5620,12 +5805,12 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mct c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Name = "test" + payload.Request.Site.Name = "test" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.name", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Domain = "test.com" + payload.Request.Site.Domain = "test.com" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.domain", ) @@ -5748,3 +5933,257 @@ func TestSetSeatNonBid(t *testing.T) { }) } } + +func TestBuildMultiBidMap(t *testing.T) { + type testCase struct { + desc string + inPrebid *openrtb_ext.ExtRequestPrebid + expected map[string]openrtb_ext.ExtMultiBid + } + testGroups := []struct { + groupDesc string + tests []testCase + }{ + { + groupDesc: "Nil or empty tests", + tests: []testCase{ + { + desc: "prebid nil, expect nil map", + inPrebid: nil, + expected: nil, + }, + { + desc: "prebid.MultiBid nil, expect nil map", + inPrebid: &openrtb_ext.ExtRequestPrebid{}, + expected: nil, + }, + { + desc: "not-nil prebid.MultiBid is empty, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{}, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + { + desc: "Uppercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "APPNEXUS"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "APPNEXUS"}, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "unknown"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "UnknownBidder"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidder: "AppNexus"}, + {Bidder: "appnexus"}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": {Bidder: "appnexus"}, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidders tests", + tests: []testCase{ + { + desc: "Lowercase prebid.MultiBid.Bidder is found in the BidderName list, entry is mapped", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"appnexus"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"appnexus"}, + }, + }, + }, + { + desc: "Lowercase prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"unknown"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Mixed-case prebid.MultiBid.Bidder is not found in the BidderName list, expect empty map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "Different-cased prebid.MultiBid.Bidder entries that refer to the same adapter are found in the BidderName list are mapped once", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + {Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}}, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidders: []string{"AppNexus", "appnexus", "UnknownBidder"}, + }, + }, + }, + }, + }, + { + groupDesc: "prebid.MultiBid.Bidder and prebid.MultiBid.Bidders entries in tests", + tests: []testCase{ + { + desc: "prebid.MultiBid.Bidder found, ignore entries in prebid.MultiBid.Bidders, even if its unknown", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "UnknownBidder", + Bidders: []string{"appnexus", "rubicon", "pubmatic"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{}, + }, + { + desc: "prebid.MultiBid.Bidder found in one entry, prebid.MultiBid.Bidders in another. Add all to map", + inPrebid: &openrtb_ext.ExtRequestPrebid{ + MultiBid: []*openrtb_ext.ExtMultiBid{ + { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + expected: map[string]openrtb_ext.ExtMultiBid{ + "pubmatic": { + Bidder: "pubmatic", + Bidders: []string{"appnexus", "rubicon", "UnknownBidder"}, + }, + "appnexus": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + "rubicon": { + Bidders: []string{"UnknownBidder", "appnexus", "rubicon"}, + }, + }, + }, + }, + }, + } + for _, group := range testGroups { + for _, tc := range group.tests { + t.Run(group.groupDesc+tc.desc, func(t *testing.T) { + multiBidMap := buildMultiBidMap(tc.inPrebid) + assert.Equal(t, tc.expected, multiBidMap, tc.desc) + }) + } + } +} + +func TestBidsToUpdate(t *testing.T) { + type testInput struct { + multiBid map[string]openrtb_ext.ExtMultiBid + bidder string + } + testCases := []struct { + desc string + in testInput + expected int + }{ + { + desc: "Empty multibid map. Expect openrtb_ext.DefaultBidLimit", + in: testInput{}, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "Empty bidder. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, + }, + }, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "bidder finds a match in multibid map but TargetBidderCodePrefix is empty. Expect openrtb_ext.DefaultBidLimit", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + }, + }, + bidder: "appnexus", + }, + expected: openrtb_ext.DefaultBidLimit, + }, + { + desc: "multibid element with non-empty TargetBidderCodePrefix matches bidder. Expect MaxBids value", + in: testInput{ + multiBid: map[string]openrtb_ext.ExtMultiBid{ + "appnexus": { + Bidder: "appnexus", + MaxBids: ptrutil.ToPtr(2), + TargetBidderCodePrefix: "aPrefix", + }, + }, + bidder: "appnexus", + }, + expected: 2, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + assert.Equal(t, tc.expected, bidsToUpdate(tc.in.multiBid, tc.in.bidder), tc.desc) + }) + } +} diff --git a/exchange/exchangetest/activity-tid-off.json b/exchange/exchangetest/activity-tid-off.json new file mode 100644 index 00000000000..174f09659ec --- /dev/null +++ b/exchange/exchangetest/activity-tid-off.json @@ -0,0 +1,85 @@ +{ + "accountPrivacy": { + "allowactivities": { + "transmitTid": { + "default": true, + "rules": [ + { + "allow": true, + "condition": { + "componentName": ["appnexus"], + "componentType":["bidder"] + } + } + ] + } + } + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "tid": "extTestTID", + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "source": { + "tid": "sourceTestTID" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "tid": "extTestTID", + "bidder": { + "placementId": 1 + } + } + } + ], + "source": { + "tid": "sourceTestTID" + } + } + }, + "mockResponse": { + "errors": [ + "appnexus-error" + ] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/activity-tid-on.json b/exchange/exchangetest/activity-tid-on.json new file mode 100644 index 00000000000..98da945bff2 --- /dev/null +++ b/exchange/exchangetest/activity-tid-on.json @@ -0,0 +1,82 @@ +{ + "accountPrivacy": { + "allowactivities": { + "transmitTid": { + "default": true, + "rules": [ + { + "allow": false, + "condition": { + "componentName": ["appnexus"], + "componentType":["bidder"] + } + } + ] + } + } + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "tid": "extTestTID", + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "source": { + "tid": "sourceTestTID" + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "source": {} + } + }, + "mockResponse": { + "errors": [ + "appnexus-error" + ] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/alternate-bidder-codes.json b/exchange/exchangetest/alternate-bidder-codes.json new file mode 100644 index 00000000000..c2e6f95a5a7 --- /dev/null +++ b/exchange/exchangetest/alternate-bidder-codes.json @@ -0,0 +1,272 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": 5890 + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "PUBmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": 5890 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "pubmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71 + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51 + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "groupm" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": null + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "groupm" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "groupm", + "bid": [ + { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51, + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "groupm" + }, + "type": "video" + } + } + }, + { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "groupm" + }, + "type": "banner" + } + } + } + ] + }, + { + "seat": "pubmatic", + "bid": [ + { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71, + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index 659045cd588..14f1181e96a 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -137,6 +137,9 @@ "origbidcpm": 0.3, "prebid": { "type": "video", + "meta": { + "adaptercode":"appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -162,6 +165,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/bid-consolidation-test.json b/exchange/exchangetest/bid-consolidation-test.json index b38e9d69603..61dbae5e045 100644 --- a/exchange/exchangetest/bid-consolidation-test.json +++ b/exchange/exchangetest/bid-consolidation-test.json @@ -127,6 +127,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "video" } } @@ -146,6 +149,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } @@ -160,6 +166,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json index 49e4f8a8f22..f6a2a065485 100644 --- a/exchange/exchangetest/bid-ext-prebid-collision.json +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -99,6 +99,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json index 69e7aba9f0c..f2416d6f2de 100644 --- a/exchange/exchangetest/bid-ext.json +++ b/exchange/exchangetest/bid-ext.json @@ -96,6 +96,9 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json index 9c5cb84c310..9b3c5eee245 100644 --- a/exchange/exchangetest/bid-id-invalid.json +++ b/exchange/exchangetest/bid-id-invalid.json @@ -109,6 +109,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json index b4a30640f77..caa02384025 100644 --- a/exchange/exchangetest/bid-id-valid.json +++ b/exchange/exchangetest/bid-id-valid.json @@ -109,6 +109,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "bidid": "mock_uuid", "type": "video", "targeting": { diff --git a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json index ea48f3a966f..a6e0de460f7 100644 --- a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json +++ b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json @@ -173,6 +173,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/bid_response_validation_warn_creative.json b/exchange/exchangetest/bid_response_validation_warn_creative.json index a170eac778e..47546683f05 100644 --- a/exchange/exchangetest/bid_response_validation_warn_creative.json +++ b/exchange/exchangetest/bid_response_validation_warn_creative.json @@ -169,6 +169,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "banner" } } @@ -188,6 +191,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/bid_response_validation_warn_secure.json b/exchange/exchangetest/bid_response_validation_warn_secure.json index a6b208d2f0d..3c06f695970 100644 --- a/exchange/exchangetest/bid_response_validation_warn_secure.json +++ b/exchange/exchangetest/bid_response_validation_warn_secure.json @@ -172,6 +172,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode":"appnexus" + }, "type": "banner" } } @@ -192,6 +195,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode":"rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/buyeruid_case_insensitive.json b/exchange/exchangetest/buyeruid_case_insensitive.json new file mode 100644 index 00000000000..6999e8c9515 --- /dev/null +++ b/exchange/exchangetest/buyeruid_case_insensitive.json @@ -0,0 +1,144 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "ext": { + "prebid": { + "buyeruids": { + "APPnexUS": "12345" + } + } + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "APPnexus": { + "placementId": 1 + }, + "appNEXUS": { + "placementId": 2 + }, + "amx": {} + } + } + } + } + ], + "ext": { + "prebid": { + "aliases": { + "APPnexus": "appnexus", + "appNEXUS": "appnexus" + } + } + } + } + }, + "outgoingRequests": { + "APPnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "buyeruid": "12345" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + } + }, + "appNEXUS": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId", + "buyeruid": "12345" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 2 + } + } + } + ] + } + } + }, + "amx": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "userId" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "ext": {} + } + } +} diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index d803759d1d9..5c0e029c975 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -145,6 +145,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -171,6 +174,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 054737e653e..11a63f5db2e 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -147,6 +147,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -173,6 +176,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json index f10bdcf86c0..3dfe441567e 100644 --- a/exchange/exchangetest/eidpermissions-allowed-alias.json +++ b/exchange/exchangetest/eidpermissions-allowed-alias.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -66,7 +70,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -127,6 +135,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "foo" + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json new file mode 100644 index 00000000000..459bb015e25 --- /dev/null +++ b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json @@ -0,0 +1,147 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "ext": { + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] + } + }, + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "source1", + "bidders": [ + "APPNEXUS" + ] + } + ] + } + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "ext": { + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "video" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json index 6b27212cbcf..06b2d184bf1 100644 --- a/exchange/exchangetest/eidpermissions-allowed.json +++ b/exchange/exchangetest/eidpermissions-allowed.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -63,7 +67,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -124,6 +132,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index df112905235..72431595fe3 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -10,7 +10,11 @@ "eids": [ { "source": "source1", - "id": "anyId" + "uids": [ + { + "id": "id1" + } + ] } ] } @@ -115,6 +119,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-off.json b/exchange/exchangetest/events-bid-account-off-request-off.json index b15b33a8653..4ae1881b865 100644 --- a/exchange/exchangetest/events-bid-account-off-request-off.json +++ b/exchange/exchangetest/events-bid-account-off-request-off.json @@ -79,6 +79,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } @@ -93,6 +96,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-on.json b/exchange/exchangetest/events-bid-account-off-request-on.json index 8a6b8aa7e14..20606002619 100644 --- a/exchange/exchangetest/events-bid-account-off-request-on.json +++ b/exchange/exchangetest/events-bid-account-off-request-on.json @@ -82,6 +82,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -100,6 +103,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-bid-account-on-request-off.json b/exchange/exchangetest/events-bid-account-on-request-off.json index 5a2fa6ae4d2..0447a2240e3 100644 --- a/exchange/exchangetest/events-bid-account-on-request-off.json +++ b/exchange/exchangetest/events-bid-account-on-request-off.json @@ -81,6 +81,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -99,6 +102,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index a94546d2aa0..3792c0ba95b 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -125,6 +125,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -145,6 +148,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -168,6 +174,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 275b8aef2ce..1a5aea4217f 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -131,6 +131,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "bidid": "mock_uuid", "type": "video" } @@ -154,6 +157,9 @@ "origbidcpm": 0.71, "prebid": { "bidid": "mock_uuid", + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -178,6 +184,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "bidid": "mock_uuid", "type": "video" } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index e271ecf5c8f..38f407d559b 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -126,6 +126,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -147,6 +150,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -171,6 +177,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json index b46f54c6408..8da9a29095a 100644 --- a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json +++ b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json @@ -185,7 +185,7 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video", "targeting": { @@ -214,7 +214,7 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video" } @@ -231,7 +231,7 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pmbidder" + "adaptercode": "groupm" }, "type": "video", "targeting": { diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json index 23e122593b5..edf2e60b124 100644 --- a/exchange/exchangetest/extra-bids.json +++ b/exchange/exchangetest/extra-bids.json @@ -309,7 +309,7 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" + "adaptercode": "groupm" }, "type": "video", "targeting": { @@ -333,7 +333,7 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" + "adaptercode": "groupm" }, "type": "banner" } diff --git a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json index 627c95f9d54..22e0fb8229f 100644 --- a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json @@ -121,6 +121,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index f15fea59d87..37d5ade5c45 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -188,6 +188,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } @@ -207,6 +210,9 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + "adaptercode": "rubicon" + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 9eb6a77eed5..1b6b2043d26 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -121,6 +121,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner" } } diff --git a/exchange/exchangetest/fledge-with-bids.json b/exchange/exchangetest/fledge-with-bids.json index 0f998e69ee0..df59c0d28c7 100644 --- a/exchange/exchangetest/fledge-with-bids.json +++ b/exchange/exchangetest/fledge-with-bids.json @@ -114,6 +114,9 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + "adaptercode": "openx" + }, "type": "video" } } diff --git a/exchange/exchangetest/floors_enforcement.json b/exchange/exchangetest/floors_enforcement.json index 90d25301e65..2124bcf2aac 100644 --- a/exchange/exchangetest/floors_enforcement.json +++ b/exchange/exchangetest/floors_enforcement.json @@ -136,6 +136,9 @@ "ext": { "origbidcpm": 12, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "floors": { "floorCurrency": "USD", "floorRule": "*|*", diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json index 9d537f74902..c0904448375 100644 --- a/exchange/exchangetest/include-brand-category.json +++ b/exchange/exchangetest/include-brand-category.json @@ -134,6 +134,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -160,6 +163,9 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json index 7d119b23044..4e532d1e72f 100644 --- a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json @@ -196,6 +196,9 @@ "ext": { "origbidcpm": 15, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", @@ -223,6 +226,9 @@ "ext": { "origbidcpm": 18, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -255,6 +261,9 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-native.json b/exchange/exchangetest/mediatypepricegranularity-native.json index 57a318e1fcf..e454b500722 100644 --- a/exchange/exchangetest/mediatypepricegranularity-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-native.json @@ -164,6 +164,9 @@ "ext": { "origbidcpm": 24, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "native", "targeting": { "hb_bidder": "appnexus", @@ -191,6 +194,9 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/multi-bids-mixed-case.json b/exchange/exchangetest/multi-bids-mixed-case.json new file mode 100644 index 00000000000..44630b3068c --- /dev/null +++ b/exchange/exchangetest/multi-bids-mixed-case.json @@ -0,0 +1,449 @@ +{ + "description": "incoming req.ext.prebid.multibid comes with a mixed case bidder name. Expect the same bidder name casing in the outgoing ext.prebid.multibid field", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "5890" + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "includewinners": true, + "includebidderkeys": true, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + } + }, + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + }, + { + "bidders": [ + "appnexus", + "someBidder" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": "5890" + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidder": "PUBmatic", + "maxbids": 2, + "targetbiddercodeprefix": "pubm" + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + }, + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4" + }, + "bidType": "video", + "bidMeta": { + "adaptercode": "pubmatic" + } + } + ], + "seat": "pubmatic" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "multibid": [ + { + "bidders": [ + "appnexus" + ], + "maxbids": 2 + } + ] + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + }, + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2" + }, + "bidType": "banner", + "bidMeta": { + "adaptercode": "appnexus" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "pubmatic", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_pubmatic": "0.70", + "hb_size": "200x250", + "hb_size_pubmatic": "200x250" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder": "pubmatic", + "hb_bidder_pubmatic": "pubmatic", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_pubmat": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_pubmat": "/pbcache/endpoint", + "hb_pb": "0.60", + "hb_pb_pubmatic": "0.60", + "hb_size": "300x500", + "hb_size_pubmatic": "300x500" + }, + "targetbiddercode": "pubmatic" + } + } + }, + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "type": "video", + "targeting": { + "hb_bidder_pubm2": "pubm2", + "hb_cache_host_pubm2": "www.pbcserver.com", + "hb_cache_path_pubm2": "/pbcache/endpoint", + "hb_pb_pubm2": "0.50", + "hb_size_pubm2": "200x250" + }, + "targetbiddercode": "pubm2" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "targetbiddercode": "appnexus", + "type": "banner", + "targeting": { + "hb_bidder_appnexus": "appnexus", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb_appnexus": "0.20", + "hb_size_appnexus": "200x500" + } + } + } + }, + { + "id": "apn-bid-2", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-a-2", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/passthrough_imp_only.json b/exchange/exchangetest/passthrough_imp_only.json index b7b2270270b..bc3d8dd22de 100644 --- a/exchange/exchangetest/passthrough_imp_only.json +++ b/exchange/exchangetest/passthrough_imp_only.json @@ -144,6 +144,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 @@ -162,6 +165,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/passthrough_root_and_imp.json b/exchange/exchangetest/passthrough_root_and_imp.json index 7d4fe89b6d2..959b934f857 100644 --- a/exchange/exchangetest/passthrough_root_and_imp.json +++ b/exchange/exchangetest/passthrough_root_and_imp.json @@ -106,6 +106,9 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 diff --git a/exchange/exchangetest/passthrough_root_only.json b/exchange/exchangetest/passthrough_root_only.json index 0b444535348..254b09adc0e 100644 --- a/exchange/exchangetest/passthrough_root_only.json +++ b/exchange/exchangetest/passthrough_root_only.json @@ -101,6 +101,9 @@ "crid": "creative-1", "ext": { "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" }, "someField": "someValue", diff --git a/exchange/exchangetest/request-ext-prebid-filtering.json b/exchange/exchangetest/request-ext-prebid-filtering.json index 499f779af26..8a82f4cb8ab 100644 --- a/exchange/exchangetest/request-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-ext-prebid-filtering.json @@ -60,6 +60,17 @@ "rubicon": { "not": "permitted-for-appnexus" } + }, + "sdk": { + "renderers": [ + { + "name": "test-name", + "version": "test-version", + "data" : { + "complex": "data" + } + } + ] } } } @@ -108,6 +119,17 @@ "bidderparams": { "param1": 1, "paramA": "A" + }, + "sdk": { + "renderers": [ + { + "name": "test-name", + "version": "test-version", + "data": { + "complex": "data" + } + } + ] } } } @@ -152,6 +174,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/request-imp-ext-prebid-filtering.json b/exchange/exchangetest/request-imp-ext-prebid-filtering.json index fb8e0892aa5..94ac6ec057f 100644 --- a/exchange/exchangetest/request-imp-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-imp-ext-prebid-filtering.json @@ -124,6 +124,9 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json index 8f41286b3eb..6eeaf58dba1 100644 --- a/exchange/exchangetest/request-multi-bidders-debug-info.json +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -154,6 +154,9 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json index c1fe595ad5f..fc55ebd369d 100644 --- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json +++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json @@ -126,6 +126,9 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json index 43b65e59c12..91fb5817c7a 100644 --- a/exchange/exchangetest/targeting-cache-vast-banner.json +++ b/exchange/exchangetest/targeting-cache-vast-banner.json @@ -87,6 +87,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json index 2f59ba30878..1c7cb636995 100644 --- a/exchange/exchangetest/targeting-cache-vast.json +++ b/exchange/exchangetest/targeting-cache-vast.json @@ -88,6 +88,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json index ea062fbe277..7f94f73f52d 100644 --- a/exchange/exchangetest/targeting-cache-zero.json +++ b/exchange/exchangetest/targeting-cache-zero.json @@ -90,6 +90,9 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index 914a557bca9..9cc2b1ebbc0 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -179,6 +182,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -207,6 +213,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -221,6 +230,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-no-winners.json b/exchange/exchangetest/targeting-no-winners.json index 6ab69fb4773..92526022f38 100644 --- a/exchange/exchangetest/targeting-no-winners.json +++ b/exchange/exchangetest/targeting-no-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +181,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", @@ -199,6 +205,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -213,6 +222,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/targeting-only-winners.json b/exchange/exchangetest/targeting-only-winners.json index f9f96515452..5e6d43ce236 100644 --- a/exchange/exchangetest/targeting-only-winners.json +++ b/exchange/exchangetest/targeting-only-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video" } } @@ -171,6 +174,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -192,6 +198,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -206,6 +215,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-with-winners.json b/exchange/exchangetest/targeting-with-winners.json index d1a0f49f862..14ad8ef4b4e 100644 --- a/exchange/exchangetest/targeting-with-winners.json +++ b/exchange/exchangetest/targeting-with-winners.json @@ -152,6 +152,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + "adaptercode": "audienceNetwork" + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +181,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -204,6 +210,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video" } } @@ -218,6 +227,9 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + "adaptercode": "appnexus" + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/gdpr.go b/exchange/gdpr.go index d503eb5da27..52fb860f5df 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,9 +3,9 @@ package exchange import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v2/privacy/gpp" ) // getGDPR will pull the gdpr flag from an openrtb request diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index 44573b59167..0e12ec66568 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -7,8 +7,8 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go index af9e46b20fe..ee3104605d7 100644 --- a/exchange/price_granularity.go +++ b/exchange/price_granularity.go @@ -1,10 +1,11 @@ package exchange import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" "math" "strconv" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // GetPriceBucket is the externally facing function for computing CPM buckets diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go index 4f9337aadc3..810dbcdf45a 100644 --- a/exchange/price_granularity_test.go +++ b/exchange/price_granularity_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go index 463a4595c85..78c1b23e3f3 100644 --- a/exchange/seat_non_bids.go +++ b/exchange/seat_non_bids.go @@ -1,8 +1,8 @@ package exchange import ( - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type nonBids struct { diff --git a/exchange/seat_non_bids_test.go b/exchange/seat_non_bids_test.go index d9f7aa88ca0..1a6b488b542 100644 --- a/exchange/seat_non_bids_test.go +++ b/exchange/seat_non_bids_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/targeting.go b/exchange/targeting.go index dbbf10041c9..d278c2f5873 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const MaxKeyLength = 20 diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index a5f49689349..8742a4f5d2a 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,15 +8,16 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" @@ -182,7 +183,7 @@ func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.B paramsPrebid["bidder"] = paramsPrebidBidders params["prebid"] = paramsPrebid - ext, err := json.Marshal(params) + ext, err := jsonutil.Marshal(params) if err != nil { t.Fatalf("Failed to make imp exts: %v", err) } @@ -224,7 +225,7 @@ func buildBidMap(seatBids []openrtb2.SeatBid, numBids int) map[string]*openrtb2. func parseTargets(t *testing.T, bid *openrtb2.Bid) map[string]string { t.Helper() var parsed openrtb_ext.ExtBid - if err := json.Unmarshal(bid.Ext, &parsed); err != nil { + if err := jsonutil.UnmarshalValid(bid.Ext, &parsed); err != nil { t.Fatalf("Unexpected error parsing targeting params: %v", err) } return parsed.Prebid.Targeting diff --git a/exchange/tmax_adjustments.go b/exchange/tmax_adjustments.go new file mode 100644 index 00000000000..55e2b18ad01 --- /dev/null +++ b/exchange/tmax_adjustments.go @@ -0,0 +1,63 @@ +package exchange + +import ( + "context" + "time" + + "github.com/prebid/prebid-server/v2/config" +) + +type TmaxAdjustmentsPreprocessed struct { + BidderNetworkLatencyBuffer uint + PBSResponsePreparationDuration uint + BidderResponseDurationMin uint + + IsEnforced bool +} + +func ProcessTMaxAdjustments(adjustmentsConfig config.TmaxAdjustments) *TmaxAdjustmentsPreprocessed { + if !adjustmentsConfig.Enabled { + return nil + } + + isEnforced := adjustmentsConfig.BidderResponseDurationMin != 0 && + (adjustmentsConfig.BidderNetworkLatencyBuffer != 0 || adjustmentsConfig.PBSResponsePreparationDuration != 0) + + tmax := &TmaxAdjustmentsPreprocessed{ + BidderNetworkLatencyBuffer: adjustmentsConfig.BidderNetworkLatencyBuffer, + PBSResponsePreparationDuration: adjustmentsConfig.PBSResponsePreparationDuration, + BidderResponseDurationMin: adjustmentsConfig.BidderResponseDurationMin, + IsEnforced: isEnforced, + } + + return tmax +} + +type bidderTmaxContext interface { + Deadline() (deadline time.Time, ok bool) + RemainingDurationMS(deadline time.Time) int64 + Until(t time.Time) time.Duration +} +type bidderTmaxCtx struct{ ctx context.Context } + +func (b *bidderTmaxCtx) RemainingDurationMS(deadline time.Time) int64 { + return time.Until(deadline).Milliseconds() +} +func (b *bidderTmaxCtx) Deadline() (deadline time.Time, ok bool) { + deadline, ok = b.ctx.Deadline() + return +} + +// Until returns the remaining duration until the specified time +func (b *bidderTmaxCtx) Until(t time.Time) time.Duration { + return time.Until(t) +} + +func getBidderTmax(ctx bidderTmaxContext, requestTmaxMS int64, tmaxAdjustments TmaxAdjustmentsPreprocessed) int64 { + if tmaxAdjustments.IsEnforced { + if deadline, ok := ctx.Deadline(); ok { + return ctx.RemainingDurationMS(deadline) - int64(tmaxAdjustments.BidderNetworkLatencyBuffer) - int64(tmaxAdjustments.PBSResponsePreparationDuration) + } + } + return requestTmaxMS +} diff --git a/exchange/tmax_adjustments_test.go b/exchange/tmax_adjustments_test.go new file mode 100644 index 00000000000..ce6f1736adf --- /dev/null +++ b/exchange/tmax_adjustments_test.go @@ -0,0 +1,108 @@ +package exchange + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestGetBidderTmax(t *testing.T) { + var ( + requestTmaxMS int64 = 700 + bidderNetworkLatencyBuffer uint = 50 + responsePreparationDuration uint = 60 + ) + requestTmaxNS := requestTmaxMS * int64(time.Millisecond) + startTime := time.Date(2023, 5, 30, 1, 0, 0, 0, time.UTC) + deadline := time.Date(2023, 5, 30, 1, 0, 0, int(requestTmaxNS), time.UTC) + ctx := &mockBidderTmaxCtx{startTime: startTime, deadline: deadline, ok: true} + tests := []struct { + description string + ctx bidderTmaxContext + requestTmax int64 + expectedTmax int64 + tmaxAdjustments TmaxAdjustmentsPreprocessed + }{ + { + description: "returns-requestTmax-when-IsEnforced-is-false", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: false}, + expectedTmax: requestTmaxMS, + }, + { + description: "returns-requestTmax-when-BidderResponseDurationMin-is-not-set", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 0}, + expectedTmax: requestTmaxMS, + }, + { + description: "returns-requestTmax-when-BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration-is-not-set", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0}, + expectedTmax: requestTmaxMS, + }, + { + description: "returns-requestTmax-when-context-deadline-is-not-set", + ctx: &mockBidderTmaxCtx{ok: false}, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 50, PBSResponsePreparationDuration: 60}, + expectedTmax: requestTmaxMS, + }, + { + description: "returns-remaing-duration-by-subtracting-BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration", + ctx: ctx, + requestTmax: requestTmaxMS, + tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: bidderNetworkLatencyBuffer, PBSResponsePreparationDuration: responsePreparationDuration}, + expectedTmax: ctx.RemainingDurationMS(deadline) - int64(bidderNetworkLatencyBuffer) - int64(responsePreparationDuration), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + assert.Equal(t, test.expectedTmax, getBidderTmax(test.ctx, test.requestTmax, test.tmaxAdjustments)) + }) + } +} + +func TestProcessTMaxAdjustments(t *testing.T) { + tests := []struct { + description string + expected *TmaxAdjustmentsPreprocessed + tmaxAdjustments config.TmaxAdjustments + }{ + { + description: "returns-nil-when-tmax-is-not-enabled", + tmaxAdjustments: config.TmaxAdjustments{Enabled: false}, + expected: nil, + }, + { + description: "BidderResponseDurationMin-is-not-set", + tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 0, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 20}, + expected: &TmaxAdjustmentsPreprocessed{IsEnforced: false, BidderResponseDurationMin: 0, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 20}, + }, + { + description: "BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration-are-not-set", + tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0}, + expected: &TmaxAdjustmentsPreprocessed{IsEnforced: false, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0}, + }, + { + description: "BidderNetworkLatencyBuffer-is-not-set", + tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 10}, + expected: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 10}, + }, + { + description: "PBSResponsePreparationDuration-is-not-set", + tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 0}, + expected: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 0}, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + assert.Equal(t, test.expected, ProcessTMaxAdjustments(test.tmaxAdjustments)) + }) + } +} diff --git a/exchange/utils.go b/exchange/utils.go index d43a4182451..53890718a1d 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,7 +6,9 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/v2/ortb" "math/rand" + "strings" "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/vendorconsent" @@ -14,25 +16,27 @@ import ( gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/privacy/ccpa" + "github.com/prebid/prebid-server/v2/privacy/lmt" + "github.com/prebid/prebid-server/v2/schain" + "github.com/prebid/prebid-server/v2/stored_responses" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) var channelTypeMap = map[metrics.RequestType]config.ChannelType{ - metrics.ReqTypeAMP: config.ChannelAMP, - metrics.ReqTypeORTB2App: config.ChannelApp, - metrics.ReqTypeVideo: config.ChannelVideo, - metrics.ReqTypeORTB2Web: config.ChannelWeb, + metrics.ReqTypeAMP: config.ChannelAMP, + metrics.ReqTypeORTB2App: config.ChannelApp, + metrics.ReqTypeVideo: config.ChannelVideo, + metrics.ReqTypeORTB2Web: config.ChannelWeb, + metrics.ReqTypeORTB2DOOH: config.ChannelDOOH, } const unknownBidder string = "" @@ -54,9 +58,8 @@ type requestSplitter struct { func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, auctionReq AuctionRequest, requestExt *openrtb_ext.ExtRequest, - gdprDefaultValue gdpr.Signal, + gdprDefaultValue gdpr.Signal, bidAdjustmentFactors map[string]float64, ) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { - req := auctionReq.BidRequestWrapper aliases, errs := parseAliases(req.BidRequest) if len(errs) > 0 { @@ -93,6 +96,11 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, } } + if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { + //Apply BidAdjustmentFactor to imp.BidFloor + applyBidAdjustmentToFloor(allBidderRequests, bidAdjustmentFactors) + } + gdprSignal, err := getGDPR(req) if err != nil { errs = append(errs, err) @@ -112,15 +120,13 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, lmtEnforcer := extractLMT(req.BidRequest, rs.privacyConfig) // request level privacy policies - privacyEnforcement := privacy.Enforcement{ - COPPA: req.BidRequest.Regs != nil && req.BidRequest.Regs.COPPA == 1, - LMT: lmtEnforcer.ShouldEnforce(unknownBidder), - } + coppa := req.BidRequest.Regs != nil && req.BidRequest.Regs.COPPA == 1 + lmt := lmtEnforcer.ShouldEnforce(unknownBidder) privacyLabels.CCPAProvided = ccpaEnforcer.CanEnforce() privacyLabels.CCPAEnforced = ccpaEnforcer.ShouldEnforce(unknownBidder) - privacyLabels.COPPAEnforced = privacyEnforcement.COPPA - privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder) + privacyLabels.COPPAEnforced = coppa + privacyLabels.LMTEnforced = lmt var gdprEnforced bool var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{} @@ -148,37 +154,80 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, // bidder level privacy policies for _, bidderRequest := range allBidderRequests { - bidRequestAllowed := true + // fetchBids activity + scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()} + fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req)) + if !fetchBidsActivityAllowed { + // skip the call to a bidder if fetchBids activity is not allowed + // do not add this bidder to allowedBidderRequests + continue + } - // CCPA - privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) + var auctionPermissions gdpr.AuctionPermissions + var gdprErr error - // GDPR if gdprEnforced { - auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) - bidRequestAllowed = auctionPermissions.AllowBidRequest - - if err == nil { - privacyEnforcement.GDPRGeo = !auctionPermissions.PassGeo - privacyEnforcement.GDPRID = !auctionPermissions.PassID - } else { - privacyEnforcement.GDPRGeo = true - privacyEnforcement.GDPRID = true + auctionPermissions, gdprErr = gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) + if !auctionPermissions.AllowBidRequest { + // auction request is not permitted by GDPR + // do not add this bidder to allowedBidderRequests + rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) + continue } + } - if !bidRequestAllowed { - rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) + ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config} + + // FPD should be applied before policies, otherwise it overrides policies and activities restricted data + applyFPD(auctionReq.FirstPartyData, bidderRequest) + + reqWrapper := ortb.CloneBidderReq(bidderRequest.BidRequest) + + passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req)) + if !passIDActivityAllowed { + //UFPD + privacy.ScrubUserFPD(reqWrapper) + } else { + // run existing policies (GDPR, CCPA, COPPA, LMT) + // potentially block passing IDs based on GDPR + if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) { + privacy.ScrubGdprID(reqWrapper) + } + // potentially block passing IDs based on CCPA + if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) + } + } + + passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req)) + if !passGeoActivityAllowed { + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) + } else { + // run existing policies (GDPR, CCPA, COPPA, LMT) + // potentially block passing geo based on GDPR + if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassGeo) { + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) + } + // potentially block passing geo based on CCPA + if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) } } - if auctionReq.FirstPartyData != nil && auctionReq.FirstPartyData[bidderRequest.BidderName] != nil { - applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) + if lmt || coppa { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa) } - if bidRequestAllowed { - privacyEnforcement.Apply(bidderRequest.BidRequest) - allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) + if !passTIDAllowed { + privacy.ScrubTID(reqWrapper) } + + reqWrapper.RebuildRequest() + bidderRequest.BidRequest = reqWrapper.BidRequest + + allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + // GPP downgrade: always downgrade unless we can confirm GPP is supported if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) { setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) @@ -240,7 +289,7 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j reqExt := &openrtb_ext.ExtRequest{} if len(bidRequest.Ext) > 0 { - err := json.Unmarshal(bidRequest.Ext, &reqExt) + err := jsonutil.Unmarshal(bidRequest.Ext, &reqExt) if err != nil { return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) } @@ -251,7 +300,7 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j } var bidderParams map[string]json.RawMessage - err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + err := jsonutil.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) if err != nil { return nil, err } @@ -283,9 +332,15 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, return nil, []error{err} } + lowerCaseExplicitBuyerUIDs := make(map[string]string) + for bidder, uid := range explicitBuyerUIDs { + lowerKey := strings.ToLower(bidder) + lowerCaseExplicitBuyerUIDs[lowerKey] = uid + } + var errs []error for bidder, imps := range impsByBidder { - coreBidder := resolveBidder(bidder, aliases) + coreBidder, isRequestAlias := resolveBidder(bidder, aliases) reqCopy := *req.BidRequest reqCopy.Imp = imps @@ -305,6 +360,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, bidderRequest := BidderRequest{ BidderName: openrtb_ext.BidderName(bidder), BidderCoreName: coreBidder, + IsRequestAlias: isRequestAlias, BidRequest: &reqCopy, BidderLabels: metrics.AdapterLabels{ Source: auctionRequest.LegacyLabels.Source, @@ -317,7 +373,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest, } syncerKey := bidderToSyncerKey[string(coreBidder)] - if hadSync := prepareUser(&reqCopy, bidder, syncerKey, explicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { + if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo } else { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes @@ -354,16 +410,17 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request } if requestExtParsed != nil { - prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions - prebid.Integration = requestExtParsed.Prebid.Integration prebid.Channel = requestExtParsed.Prebid.Channel + prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions prebid.Debug = requestExtParsed.Prebid.Debug - prebid.Server = requestExtParsed.Prebid.Server + prebid.Integration = requestExtParsed.Prebid.Integration prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes) + prebid.Sdk = requestExtParsed.Prebid.Sdk + prebid.Server = requestExtParsed.Prebid.Server } // Marshal New Prebid Object - prebidJson, err := json.Marshal(prebid) + prebidJson, err := jsonutil.Marshal(prebid) if err != nil { return nil, err } @@ -371,7 +428,7 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request // Parse Existing Ext extMap := make(map[string]json.RawMessage) if len(requestExt) != 0 { - if err := json.Unmarshal(requestExt, &extMap); err != nil { + if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil { return nil, err } } @@ -384,37 +441,39 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request } if len(extMap) > 0 { - return json.Marshal(extMap) + return jsonutil.Marshal(extMap) } else { return nil, nil } } func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { - if reqABC != nil { - alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: reqABC.Enabled, - } - if bidderCodes, ok := reqABC.Bidders[bidder]; ok { - alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - bidder: bidderCodes, - } - } - return alternateBidderCodes + + if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil { + return altBidderCodes } - if accABC != nil { + if altBidderCodes := copyExtAlternateBidderCodes(bidder, accABC); altBidderCodes != nil { + return altBidderCodes + } + + return nil +} + +func copyExtAlternateBidderCodes(bidder string, altBidderCodes *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { + if altBidderCodes != nil { alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: accABC.Enabled, + Enabled: altBidderCodes.Enabled, } - if bidderCodes, ok := accABC.Bidders[bidder]; ok { + + if bidderCodes, ok := altBidderCodes.IsBidderInAlternateBidderCodes(bidder); ok { alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ bidder: bidderCodes, } } + return alternateBidderCodes } - return nil } @@ -422,12 +481,12 @@ func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMulti adapterMultiBid := make([]*openrtb_ext.ExtMultiBid, 0) for _, multiBid := range reqMultiBid { if multiBid.Bidder != "" { - if multiBid.Bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, multiBid.Bidder, adapterABC) { + if strings.ToLower(multiBid.Bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(multiBid.Bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, multiBid) } } else { for _, bidder := range multiBid.Bidders { - if bidder == adapter || isBidderInExtAlternateBidderCodes(adapter, bidder, adapterABC) { + if strings.ToLower(bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(bidder), adapterABC) { adapterMultiBid = append(adapterMultiBid, &openrtb_ext.ExtMultiBid{ Bidders: []string{bidder}, MaxBids: multiBid.MaxBids, @@ -468,7 +527,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { } var userExt openrtb_ext.ExtUser - if err := json.Unmarshal(user.Ext, &userExt); err != nil { + if err := jsonutil.Unmarshal(user.Ext, &userExt); err != nil { return nil, err } if userExt.Prebid == nil { @@ -482,7 +541,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { // Remarshal (instead of removing) if the ext has other known fields if userExt.Consent != "" || len(userExt.Eids) > 0 { - if newUserExtBytes, err := json.Marshal(userExt); err != nil { + if newUserExtBytes, err := jsonutil.Marshal(userExt); err != nil { return nil, err } else { user.Ext = newUserExtBytes @@ -506,20 +565,20 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { for i, imp := range imps { var impExt map[string]json.RawMessage - if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + if err := jsonutil.UnmarshalValid(imp.Ext, &impExt); err != nil { return nil, fmt.Errorf("invalid json for imp[%d]: %v", i, err) } var impExtPrebid map[string]json.RawMessage if impExtPrebidJSON, exists := impExt[openrtb_ext.PrebidExtKey]; exists { // validation already performed by impExt unmarshal. no error is possible here, proven by tests. - json.Unmarshal(impExtPrebidJSON, &impExtPrebid) + jsonutil.Unmarshal(impExtPrebidJSON, &impExtPrebid) } var impExtPrebidBidder map[string]json.RawMessage if impExtPrebidBidderJSON, exists := impExtPrebid[openrtb_ext.PrebidExtBidderKey]; exists { // validation already performed by impExt unmarshal. no error is possible here, proven by tests. - json.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) + jsonutil.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) } sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) @@ -532,7 +591,7 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt - impExtJSON, err := json.Marshal(sanitizedImpExt) + impExtJSON, err := jsonutil.Marshal(sanitizedImpExt) if err != nil { return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: cannot marshal ext: %v", i, err) } @@ -572,7 +631,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map // marshal sanitized imp[].ext.prebid if len(sanitizedImpPrebidExt) > 0 { - if impExtPrebidJSON, err := json.Marshal(sanitizedImpPrebidExt); err == nil { + if impExtPrebidJSON, err := jsonutil.Marshal(sanitizedImpPrebidExt); err == nil { sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON } else { return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) @@ -597,7 +656,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) - if id, ok := explicitBuyerUIDs[givenBidder]; ok { + if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok { req.User = copyWithBuyerUID(req.User, id) } else if hadCookie { req.User = copyWithBuyerUID(req.User, cookieId) @@ -636,7 +695,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque // low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive. var userExt map[string]json.RawMessage - if err := json.Unmarshal(request.User.Ext, &userExt); err != nil { + if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil { return err } @@ -646,7 +705,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque } var eids []openrtb2.EID - if err := json.Unmarshal(eidsJSON, &eids); err != nil { + if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil { return err } @@ -666,7 +725,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque allowed := false if rule, hasRule := eidRules[eid.Source]; hasRule { for _, ruleBidder := range rule { - if ruleBidder == "*" || ruleBidder == bidder { + if ruleBidder == "*" || strings.EqualFold(ruleBidder, bidder) { allowed = true break } @@ -689,7 +748,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque if len(eidsAllowed) == 0 { delete(userExt, "eids") } else { - eidsRaw, err := json.Marshal(eidsAllowed) + eidsRaw, err := jsonutil.Marshal(eidsAllowed) if err != nil { return err } @@ -702,7 +761,7 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque return nil } - userExtJSON, err := json.Marshal(userExt) + userExtJSON, err := jsonutil.Marshal(userExt) if err != nil { return err } @@ -717,18 +776,21 @@ func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessag } // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. -func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName { - if coreBidder, ok := aliases[bidder]; ok { - return openrtb_ext.BidderName(coreBidder) +func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) { + normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) + + if coreBidder, ok := requestAliases[bidder]; ok { + return openrtb_ext.BidderName(coreBidder), true } - return openrtb_ext.BidderName(bidder) + + return normalisedBidderName, false } // parseAliases parses the aliases from the BidRequest func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { var aliases map[string]string if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliases"); dataType == jsonparser.Object && err == nil { - if err := json.Unmarshal(value, &aliases); err != nil { + if err := jsonutil.Unmarshal(value, &aliases); err != nil { return nil, []error{err} } } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { @@ -741,7 +803,7 @@ func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { func parseAliasesGVLIDs(orig *openrtb2.BidRequest) (map[string]uint16, []error) { var aliasesGVLIDs map[string]uint16 if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliasgvlids"); dataType == jsonparser.Object && err == nil { - if err := json.Unmarshal(value, &aliasesGVLIDs); err != nil { + if err := jsonutil.Unmarshal(value, &aliasesGVLIDs); err != nil { return nil, []error{err} } } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { @@ -847,26 +909,46 @@ func parseRequestDebugValues(test int8, requestExtPrebid *openrtb_ext.ExtRequest } func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) map[string]float64 { - if requestExtPrebid != nil { - return requestExtPrebid.BidAdjustmentFactors + if requestExtPrebid != nil && requestExtPrebid.BidAdjustmentFactors != nil { + caseInsensitiveMap := make(map[string]float64, len(requestExtPrebid.BidAdjustmentFactors)) + for bidder, bidAdjFactor := range requestExtPrebid.BidAdjustmentFactors { + caseInsensitiveMap[strings.ToLower(bidder)] = bidAdjFactor + } + return caseInsensitiveMap } return nil } -func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRequest) { - if fpd.Site != nil { - bidReq.Site = fpd.Site +func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) { + if fpd == nil { + return + } + + bidder := r.BidderCoreName + if r.IsRequestAlias { + bidder = r.BidderName + } + + fpdToApply, exists := fpd[bidder] + if !exists || fpdToApply == nil { + return } - if fpd.App != nil { - bidReq.App = fpd.App + + if fpdToApply.Site != nil { + r.BidRequest.Site = fpdToApply.Site } - if fpd.User != nil { + + if fpdToApply.App != nil { + r.BidRequest.App = fpdToApply.App + } + + if fpdToApply.User != nil { //BuyerUID is a value obtained between fpd extraction and fpd application. //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request - if bidReq.User != nil && len(bidReq.User.BuyerUID) > 0 { - fpd.User.BuyerUID = bidReq.User.BuyerUID + if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 { + fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID } - bidReq.User = fpd.User + r.BidRequest.User = fpdToApply.User } } @@ -878,13 +960,14 @@ func buildBidResponseRequest(req *openrtb2.BidRequest, bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) for bidderName, impResps := range bidderImpResponses { - resolvedBidder := resolveBidder(string(bidderName), aliases) + resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), aliases) bidderToBidderResponse[bidderName] = BidderRequest{ BidRequest: req, BidderCoreName: resolvedBidder, BidderName: bidderName, BidderStoredResponses: impResps, - ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)], + ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)], + IsRequestAlias: isRequestAlias, BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, } } @@ -1015,7 +1098,7 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.Ext != nil { var bidExt openrtb_ext.ExtBid - err = json.Unmarshal(bid.Ext, &bidExt) + err = jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil && bidExt.Prebid != nil { if bidType, err = openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)); err == nil { return bidType, nil @@ -1032,3 +1115,25 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { Message: errMsg, } } + +func applyBidAdjustmentToFloor(allBidderRequests []BidderRequest, bidAdjustmentFactors map[string]float64) { + + if len(bidAdjustmentFactors) == 0 { + return + } + + for _, bidderRequest := range allBidderRequests { + bidAdjustment := 1.0 + + if bidAdjustemntValue, ok := bidAdjustmentFactors[string(bidderRequest.BidderName)]; ok { + bidAdjustment = bidAdjustemntValue + } + + if bidAdjustment != 1.0 { + for index, imp := range bidderRequest.BidRequest.Imp { + imp.BidFloor = imp.BidFloor / bidAdjustment + bidderRequest.BidRequest.Imp[index] = imp + } + } + } +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 8bb82fe412f..1e5213ab0b1 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -8,20 +8,26 @@ import ( "sort" "testing" + "github.com/prebid/prebid-server/v2/stored_responses" + gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/firstpartydata" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +const deviceUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" + // permissionsMock mocks the Permissions interface for tests type permissionsMock struct { allowAllBidders bool @@ -73,8 +79,6 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest, assert.NotEqual(t, bidderRequests, 0, "cleanOpenRTBRequest should split request into individual bidder requests") // assert for PI data - // Both appnexus and brightroll should be allowed since brightroll - // is used as an alias for appnexus in the test request for _, req := range bidderRequests { if !applyCOPPA && consentedVendors[req.BidderName.String()] { assert.NotEqual(t, req.BidRequest.User.BuyerUID, "", "cleanOpenRTBRequest shouldn't clean PI data as per COPPA or for a consented vendor as per GDPR or per CCPA") @@ -188,21 +192,21 @@ func TestSplitImps(t *testing.T) { givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`malformed`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: expect { or n, but found m", }, { description: "Malformed imp.ext.prebid", givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": malformed}`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: do not know how to skip: 109", }, { description: "Malformed imp.ext.prebid.bidder", givenImps: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": malformed}}`)}, }, - expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + expectedError: "invalid json for imp[0]: do not know how to skip: 109", }, } @@ -404,22 +408,6 @@ func TestCreateSanitizedImpExt(t *testing.T) { }, expectedError: "", }, - { - description: "Marshal Error - imp.ext.prebid", - givenImpExt: map[string]json.RawMessage{ - "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), - }, - givenImpExtPrebid: map[string]json.RawMessage{ - "options": json.RawMessage(`malformed`), // String value without quotes. - }, - expected: nil, - expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'm' looking for beginning of value", - }, } for _, test := range testCases { @@ -458,7 +446,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { bidReqAssertions: assertReq, hasError: false, applyCOPPA: false, - consentedVendors: map[string]bool{"appnexus": true, "brightroll": true}, + consentedVendors: map[string]bool{"appnexus": true}, }, } @@ -487,7 +475,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -505,14 +493,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { App: &openrtb2.App{Name: "fpdApnApp"}, User: &openrtb2.User{Keywords: "fpdApnUser"}, } - fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd - - brightrollFpd := firstpartydata.ResolvedFirstPartyData{ - Site: &openrtb2.Site{Name: "fpdBrightrollSite"}, - App: &openrtb2.App{Name: "fpdBrightrollApp"}, - User: &openrtb2.User{Keywords: "fpdBrightrollUser"}, - } - fpd[openrtb_ext.BidderName("brightroll")] = &brightrollFpd + fpd[openrtb_ext.BidderName("rubicon")] = &apnFpd emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) @@ -560,7 +541,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) assert.Empty(t, err, "No errors should be returned") for _, bidderRequest := range bidderRequests { bidderName := bidderRequest.BidderName @@ -600,7 +581,7 @@ func TestExtractAdapterReqBidderParamsMap(t *testing.T) { name: "malformed req.ext", givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, want: nil, - wantErr: errors.New("error decoding Request.ext : invalid character 'm' looking for beginning of value"), + wantErr: errors.New("error decoding Request.ext : expect { or n, but found m"), }, { name: "extract bidder params from req.Ext for input request in adapter code", @@ -875,7 +856,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { bidderInfo: config.BidderInfos{}, } - actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) assert.Empty(t, err, "No errors should be returned") assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description) for _, actualBidderRequest := range actualBidderRequests { @@ -1044,7 +1025,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) result := bidderRequests[0] assert.Nil(t, errs) @@ -1089,7 +1070,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} var reqExtStruct openrtb_ext.ExtRequest - err := json.Unmarshal(req.Ext, &reqExtStruct) + err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct) assert.NoError(t, err, test.description+":marshal_ext") auctionReq := AuctionRequest{ @@ -1121,7 +1102,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { bidderInfo: config.BidderInfos{}, } - _, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo) + _, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, map[string]float64{}) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -1180,7 +1161,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) result := bidderRequests[0] assert.Nil(t, errs) @@ -1248,7 +1229,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") } @@ -1273,7 +1254,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1319,7 +1300,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") } @@ -1344,7 +1325,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1842,6 +1823,11 @@ func TestGetExtBidAdjustmentFactors(t *testing.T) { requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}}, outBidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}, }, + { + desc: "BidAdjustmentFactors contains uppercase bidders, expect case insensitve map returned", + requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"Bidder": 1.0, "APPNEXUS": 2.0}}, + outBidAdjustmentFactors: map[string]float64{"bidder": 1.0, "appnexus": 2.0}, + }, } for _, test := range testCases { actualBidAdjustmentFactors := getExtBidAdjustmentFactors(test.requestExtPrebid) @@ -1931,7 +1917,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { bidderInfo: config.BidderInfos{}, } - results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) result := results[0] assert.Nil(t, errs) @@ -2164,7 +2150,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { bidderInfo: config.BidderInfos{}, } - results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdprDefaultValue) + results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdprDefaultValue, map[string]float64{}) result := results[0] if test.expectError { @@ -2265,7 +2251,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { bidderInfo: config.BidderInfos{}, } - results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) // extract bidder name from each request in the results bidders := []openrtb_ext.BidderName{} @@ -2295,6 +2281,8 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { bidReq.User.ID = "" bidReq.User.BuyerUID = "" bidReq.User.Yob = 0 + bidReq.User.Gender = "" + bidReq.User.Geo = &openrtb2.Geo{Lat: 123.46} downgradedRegs := *bidReq.Regs downgradedUser := *bidReq.User @@ -2351,7 +2339,7 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { hostSChainNode: nil, bidderInfo: test.bidderInfos, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) assert.Nil(t, err, "Err should be nil") bidRequest := bidderRequests[0] assert.Equal(t, test.expectRegs, bidRequest.BidRequest.Regs) @@ -2391,14 +2379,14 @@ func TestBuildRequestExtForBidder(t *testing.T) { { description: "Prebid - Allowed Fields Only", bidderParams: nil, - requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), - expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), + requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), + expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), }, { description: "Prebid - Allowed Fields + Bidder Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, - requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), - expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "bidderparams":"bar"}}`), + requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), + expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { description: "Other", @@ -2409,8 +2397,8 @@ func TestBuildRequestExtForBidder(t *testing.T) { { description: "Prebid + Other + Bider Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, - requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}}}`), - expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "bidderparams":"bar"}}`), + requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), + expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { description: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", @@ -2494,7 +2482,7 @@ func TestBuildRequestExtForBidder(t *testing.T) { for _, test := range testCases { requestExtParsed := &openrtb_ext.ExtRequest{} if test.requestExt != nil { - err := json.Unmarshal(test.requestExt, requestExtParsed) + err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed) if !assert.NoError(t, err, test.description+":parse_ext") { continue } @@ -2535,7 +2523,7 @@ func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) assert.Equal(t, json.RawMessage(nil), actualJson) - assert.EqualError(t, actualErr, "invalid character 'm' looking for beginning of value") + assert.EqualError(t, actualErr, "expect { or n, but found m") } // newAdapterAliasBidRequest builds a BidRequest with aliases @@ -2551,14 +2539,14 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", DNT: &dnt, Language: "EN", }, Source: &openrtb2.Source{ - TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + TID: "testTID", }, User: &openrtb2.User{ ID: "our-id", @@ -2579,9 +2567,9 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: json.RawMessage(`{"appnexus": {"placementId": 1},"brightroll": {"placementId": 105}}`), + Ext: json.RawMessage(`{"appnexus": {"placementId": 1},"somealias": {"placementId": 105}}`), }}, - Ext: json.RawMessage(`{"prebid":{"aliases":{"brightroll":"appnexus"}}}`), + Ext: json.RawMessage(`{"prebid":{"aliases":{"somealias":"appnexus"}}}`), } } @@ -2595,23 +2583,36 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { }, }, Device: &openrtb2.Device{ - DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - IFA: "ifa", + UA: deviceUA, IP: "132.173.230.74", Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, }, Source: &openrtb2.Source{ - TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + TID: "testTID", }, User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{}`), + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, }, Imp: []openrtb2.Imp{{ - ID: "some-imp-id", + BidFloor: 100, + ID: "some-imp-id", Banner: &openrtb2.Banner{ Format: []openrtb2.Format{{ W: 300, @@ -2621,7 +2622,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), + Ext: json.RawMessage(`{"prebid":{"tid":"1234567", "bidder":{"appnexus": {"placementId": 1}}}}`), }}, } } @@ -2637,13 +2638,13 @@ func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", Language: "EN", }, Source: &openrtb2.Source{ - TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + TID: "testTID", }, User: &openrtb2.User{ ID: "our-id", @@ -2773,6 +2774,14 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), }, + { + description: "Allowed By Specific Bidder - Case Insensitive", + userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"BIDDERA"}}, + }, + expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + }, { description: "Allowed By All Bidders", userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), @@ -2856,17 +2865,17 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { { description: "Malformed Ext", userExt: json.RawMessage(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErr: "expect { or n, but found m", }, { description: "Malformed Eid Array Type", userExt: json.RawMessage(`{"eids":[42]}`), - expectedErr: "json: cannot unmarshal number into Go value of type openrtb2.EID", + expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4", }, { description: "Malformed Eid Item Type", userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), - expectedErr: "json: cannot unmarshal number into Go struct field EID.source of type string", + expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4", }, } @@ -3071,7 +3080,7 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { req := &openrtb2.BidRequest{ Site: &openrtb2.Site{}, Source: &openrtb2.Source{ - TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + TID: "testTID", }, Imp: []openrtb2.Imp{{ Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}}}`), @@ -3080,7 +3089,7 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { } extRequest := &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, "Error unmarshaling inExt") auctionReq := AuctionRequest{ @@ -3106,7 +3115,7 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) assert.Nil(t, errs) assert.Len(t, bidderRequests, 2, "Bid request count is not 2") @@ -3122,61 +3131,257 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") } -func TestApplyFPD(t *testing.T) { +func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { + tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + falseValue := false + testCases := []struct { + description string + gdprAccountEnabled *bool + gdprHostEnabled bool + gdpr string + gdprConsent string + gdprScrub bool + permissionsError error + gdprDefaultValue string + expectPrivacyLabels metrics.PrivacyLabels + expectError bool + bidAdjustmentFactor map[string]float64 + expectedImp []openrtb2.Imp + }{ + { + description: "BidFloor Adjustment Done for Appnexus", + gdprAccountEnabled: &falseValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprDefaultValue: "1", + expectPrivacyLabels: metrics.PrivacyLabels{ + GDPREnforced: false, + GDPRTCFVersion: "", + }, + bidAdjustmentFactor: map[string]float64{"appnexus": 0.50}, + expectedImp: []openrtb2.Imp{{ + BidFloor: 200, + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }, { + W: 300, + H: 600, + }}, + }, + Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), + }}, + }, + { + description: "bidAjustement Not provided", + gdprAccountEnabled: &falseValue, + gdprHostEnabled: true, + gdpr: "1", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprDefaultValue: "1", + expectPrivacyLabels: metrics.PrivacyLabels{ + GDPREnforced: false, + GDPRTCFVersion: "", + }, + bidAdjustmentFactor: map[string]float64{}, + expectedImp: []openrtb2.Imp{{ + BidFloor: 100, + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }, { + W: 300, + H: 600, + }}, + }, + Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), + }}, + }, + } + for _, test := range testCases { + req := newBidRequest(t) + accountConfig := config.Account{ + GDPR: config.AccountGDPR{ + Enabled: &falseValue, + }, + PriceFloors: config.AccountPriceFloors{ + AdjustForBidAdjustment: true, + }, + } + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + UserSyncs: &emptyUsersync{}, + Account: accountConfig, + } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: !test.gdprScrub, + passID: !test.gdprScrub, + activitiesError: test.permissionsError, + }, + }.Builder + reqSplitter := &requestSplitter{ + bidderToSyncerKey: map[string]string{}, + me: &metrics.MetricsEngineMock{}, + gdprPermsBuilder: gdprPermissionsBuilder, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{}, + } + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, test.bidAdjustmentFactor) + result := results[0] + assert.Nil(t, errs) + assert.Equal(t, test.expectedImp, result.BidRequest.Imp, test.description) + } +} +func TestApplyFPD(t *testing.T) { testCases := []struct { - description string - inputFpd firstpartydata.ResolvedFirstPartyData - inputRequest openrtb2.BidRequest - expectedRequest openrtb2.BidRequest + description string + inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData + inputBidderName string + inputBidderCoreName string + inputBidderIsRequestAlias bool + inputRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest }{ { - description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + description: "fpd-nil", + inputFpd: nil, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-nil", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": nil, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-notdefined", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "differentBidder": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-alias", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "alias": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "alias", + inputBidderCoreName: "bidder", + inputBidderIsRequestAlias: true, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: nil, App: nil, User: nil}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, }, { description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " + "expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, }, { - description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", - inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", - inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}}, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, - expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, - inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, - expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, + expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, }, { - description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", - inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, - inputRequest: openrtb2.BidRequest{}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, }, } for _, testCase := range testCases { - applyFPD(&testCase.inputFpd, &testCase.inputRequest) + bidderRequest := BidderRequest{ + BidderName: openrtb_ext.BidderName(testCase.inputBidderName), + BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName), + IsRequestAlias: testCase.inputBidderIsRequestAlias, + BidRequest: &testCase.inputRequest, + } + applyFPD(testCase.inputFpd, bidderRequest) assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) } } @@ -3195,17 +3400,17 @@ func Test_parseAliasesGVLIDs(t *testing.T) { "AliasGVLID Parsed Correctly", args{ orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids":{"brightroll":1}}}`), + Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":{"somealiascode":1}}}`), }, }, - map[string]uint16{"brightroll": 1}, + map[string]uint16{"somealiascode": 1}, false, }, { "AliasGVLID parsing error", args{ orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids": {"brightroll":"abc"}`), + Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids": {"somealiascode":"abc"}`), }, }, nil, @@ -3215,7 +3420,7 @@ func Test_parseAliasesGVLIDs(t *testing.T) { "Invalid AliasGVLID", args{ orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"brightroll":"appnexus"}, "aliasgvlids":"abc"}`), + Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":"abc"}`), }, }, nil, @@ -3225,7 +3430,7 @@ func Test_parseAliasesGVLIDs(t *testing.T) { "Missing AliasGVLID", args{ orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"brightroll":"appnexus"}}`), + Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}}`), }, }, nil, @@ -3431,7 +3636,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { if test.inExt != nil { req.Ext = test.inExt extRequest = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(req.Ext, extRequest) + err := jsonutil.UnmarshalValid(req.Ext, extRequest) assert.NoErrorf(t, err, test.desc+":Error unmarshaling inExt") } @@ -3456,7 +3661,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) assert.Equal(t, test.wantError, len(errs) != 0, test.desc) sort.Slice(bidderRequests, func(i, j int) bool { return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName @@ -4192,7 +4397,7 @@ func TestGetPrebidMediaTypeForBid(t *testing.T) { { description: "Invalid bid ext", inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`[true`)}, - expectedError: "Failed to parse bid mediatype for impression \"impId\", unexpected end of JSON input", + expectedError: "Failed to parse bid mediatype for impression \"impId\", expect { or n, but found [", }, { description: "Bid ext is nil", @@ -4233,7 +4438,7 @@ func TestGetMediaTypeForBid(t *testing.T) { { description: "invalid bid ext", inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid"`)}, - expectedError: "Failed to parse bid mediatype for impression \"impId\", unexpected end of JSON input", + expectedError: "Failed to parse bid mediatype for impression \"impId\", expect :, but found \x00", }, { description: "Valid bid ext with mtype native", @@ -4273,3 +4478,619 @@ func TestGetMediaTypeForBid(t *testing.T) { }) } } + +func TestCleanOpenRTBRequestsActivities(t *testing.T) { + expectedUserDefault := openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + } + expectedDeviceDefault := openrtb2.Device{ + UA: deviceUA, + IP: "132.173.230.74", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + } + + expectedSourceDefault := openrtb2.Source{ + TID: "testTID", + } + + testCases := []struct { + name string + req *openrtb2.BidRequest + privacyConfig config.AccountPrivacy + componentName string + allow bool + expectedReqNumber int + expectedUser openrtb2.User + expectedDevice openrtb2.Device + expectedSource openrtb2.Source + expectedImpExt json.RawMessage + }{ + { + name: "fetch_bids_request_with_one_bidder_allowed", + req: newBidRequest(t), + privacyConfig: getFetchBidsActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "fetch_bids_request_with_one_bidder_not_allowed", + req: newBidRequest(t), + privacyConfig: getFetchBidsActivityConfig("appnexus", false), + expectedReqNumber: 0, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_ufpd_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove user.eids, user.ext.data.*, user.data.*, user.{id, buyeruid, yob, gender} + //and device-specific IDs + name: "transmit_ufpd_deny", + req: newBidRequest(t), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "", + BuyerUID: "", + Yob: 0, + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + EIDs: nil, + Ext: json.RawMessage(`{"test":2}`), + Data: nil, + }, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + Language: "EN", + IP: "132.173.230.74", + DIDMD5: "", + IFA: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACMD5: "", + MACSHA1: "", + Geo: &openrtb2.Geo{Lat: 123.456, Lon: 11.278}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_precise_geo_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + name: "transmit_precise_geo_deny", + req: newBidRequest(t), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Geo: &openrtb2.Geo{Lat: 123.46, Lon: 11.28}, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + }, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + IP: "132.173.0.0", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: 123.46, Lon: 11.28}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_tid_allowed", + req: newBidRequest(t), + privacyConfig: getTransmitTIDActivityConfig("appnexus", true), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove source.tid and imp.ext.tid + name: "transmit_tid_deny", + req: newBidRequest(t), + privacyConfig: getTransmitTIDActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: openrtb2.Source{ + TID: "", + }, + expectedImpExt: json.RawMessage(`{"bidder": {"placementId": 1}}`), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + activities := privacy.NewActivityControl(&test.privacyConfig) + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, + UserSyncs: &emptyUsersync{}, + Activities: activities, + Account: config.Account{Privacy: config.AccountPrivacy{ + IPv6Config: config.IPv6{ + AnonKeepBits: 32, + }, + IPv4Config: config.IPv4{ + AnonKeepBits: 16, + }, + }}, + } + + bidderToSyncerKey := map[string]string{} + reqSplitter := &requestSplitter{ + bidderToSyncerKey: bidderToSyncerKey, + me: &metrics.MetricsEngineMock{}, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{}, + } + + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + assert.Empty(t, errs) + assert.Len(t, bidderRequests, test.expectedReqNumber) + + if test.expectedReqNumber == 1 { + assert.Equal(t, &test.expectedUser, bidderRequests[0].BidRequest.User) + assert.Equal(t, &test.expectedDevice, bidderRequests[0].BidRequest.Device) + assert.Equal(t, &test.expectedSource, bidderRequests[0].BidRequest.Source) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext)) + } + } + }) + } +} + +func buildDefaultActivityConfig(componentName string, allow bool) config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"bidder"}, + }, + }, + }, + } +} + +func getFetchBidsActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + FetchBids: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func getTransmitUFPDActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func getTransmitTIDActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitTids: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func TestApplyBidAdjustmentToFloor(t *testing.T) { + type args struct { + allBidderRequests []BidderRequest + bidAdjustmentFactors map[string]float64 + } + tests := []struct { + name string + args args + expectedAllBidderRequests []BidderRequest + }{ + { + name: " bidAdjustmentFactor is empty", + args: args{ + allBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + bidAdjustmentFactors: map[string]float64{}, + }, + expectedAllBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + }, + { + name: "bidAdjustmentFactor not present for request bidder", + args: args{ + allBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0}, + }, + expectedAllBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + }, + { + name: "bidAdjustmentFactor present for request bidder", + args: args{ + allBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75}, + }, + expectedAllBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + }, + }, + { + name: "bidAdjustmentFactor present only for appnexus request bidder", + args: args{ + allBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("pubmatic"), + }, + }, + bidAdjustmentFactors: map[string]float64{"appnexus": 0.75}, + }, + expectedAllBidderRequests: []BidderRequest{ + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, + }, + BidderName: openrtb_ext.BidderName("appnexus"), + }, + { + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + BidderName: openrtb_ext.BidderName("pubmatic"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + applyBidAdjustmentToFloor(tt.args.allBidderRequests, tt.args.bidAdjustmentFactors) + assert.Equal(t, tt.expectedAllBidderRequests, tt.args.allBidderRequests, tt.name) + }) + } +} + +func TestBuildRequestExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidderNameRaw string + accABC *openrtb_ext.ExtAlternateBidderCodes + reqABC *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "No biddername, nil reqABC and accABC", + in: testInput{}, + expected: nil, + }, + { + desc: "No biddername, non-nil reqABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil accABC", + in: testInput{ + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil reqABC nor accABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil accABC", + in: testInput{ + bidderNameRaw: "pubmatic", + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in accABC but reqABC comes first", + in: testInput{ + bidderNameRaw: "PUBmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"pubCode1"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode2"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in both but we prioritize reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + AllowedBidderCodes: []string{"pubCode"}, + }, + }, + }, + }, + { + desc: "nil reqABC non-nil accABC enabled and bidder matches elements in accABC", + in: testInput{ + bidderNameRaw: "APPnexus", + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "APPnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := buildRequestExtAlternateBidderCodes(tc.in.bidderNameRaw, tc.in.accABC, tc.in.reqABC) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + +func TestCopyExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "pass a nil alternateBidderCodes argument, expect nil output", + in: testInput{}, + expected: nil, + }, + { + desc: "non-nil alternateBidderCodes argument but bidder doesn't match", + in: testInput{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + { + desc: "non-nil alternateBidderCodes argument bidder is identical to one element in map", + in: testInput{ + bidder: "appnexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + { + desc: "case insensitive match, keep bidder casing in output", + in: testInput{ + bidder: "AppNexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "AppNexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := copyExtAlternateBidderCodes(tc.in.bidder, tc.in.alternateBidderCodes) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + +func TestBuildBidResponseRequestBidderName(t *testing.T) { + bidderImpResponses := stored_responses.BidderImpsWithBidResponses{ + openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)}, + openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)}, + } + + bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{ + "appnexus": {"impId1": true, "impId2": false}, + "appneXUS": {"impId3": true, "impId4": false}, + } + result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID) + + resultAppnexus := result["appnexus"] + assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus")) + assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false}) + + resultAppneXUS := result["appneXUS"] + assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS")) + assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false}) + +} diff --git a/experiment/adscert/inprocesssigner.go b/experiment/adscert/inprocesssigner.go index 604287f9ed6..eabd35ebb95 100644 --- a/experiment/adscert/inprocesssigner.go +++ b/experiment/adscert/inprocesssigner.go @@ -2,12 +2,13 @@ package adscert import ( "crypto/rand" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/discovery" "github.com/IABTechLab/adscert/pkg/adscert/signatory" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/config" - "time" + "github.com/prebid/prebid-server/v2/config" ) // inProcessSigner holds the signatory to add adsCert header to requests using in process go library diff --git a/experiment/adscert/remotesigner.go b/experiment/adscert/remotesigner.go index 3c9479560b2..d23dad201d3 100644 --- a/experiment/adscert/remotesigner.go +++ b/experiment/adscert/remotesigner.go @@ -2,12 +2,13 @@ package adscert import ( "fmt" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "time" ) // remoteSigner holds the signatory to add adsCert header to requests using remote signing server diff --git a/experiment/adscert/signer.go b/experiment/adscert/signer.go index 08b3f655fa2..f060f957149 100644 --- a/experiment/adscert/signer.go +++ b/experiment/adscert/signer.go @@ -2,10 +2,11 @@ package adscert import ( "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/logger" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) const SignHeader = "X-Ads-Cert-Auth" diff --git a/experiment/adscert/signer_test.go b/experiment/adscert/signer_test.go index d6d02175d95..fceb2e5c79c 100644 --- a/experiment/adscert/signer_test.go +++ b/experiment/adscert/signer_test.go @@ -2,10 +2,11 @@ package adscert import ( "errors" + "testing" + "github.com/IABTechLab/adscert/pkg/adscert/api" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" - "testing" ) func TestNilSigner(t *testing.T) { diff --git a/firstpartydata/extmerger.go b/firstpartydata/extmerger.go index 119fa8a4c3c..f3196bea996 100644 --- a/firstpartydata/extmerger.go +++ b/firstpartydata/extmerger.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/firstpartydata/extmerger_test.go b/firstpartydata/extmerger_test.go index 784163ac313..4107b0d1144 100644 --- a/firstpartydata/extmerger_test.go +++ b/firstpartydata/extmerger_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" "github.com/stretchr/testify/assert" ) diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index 0fde931d445..8e482ce700b 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -7,10 +7,11 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/ortb" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) const ( @@ -120,7 +121,9 @@ func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb } } else { // only bidders in global bidder list will receive global data and bidder specific data - for _, bidderName := range biddersWithGlobalFPD { + for _, bidder := range biddersWithGlobalFPD { + bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder) + if _, present := allBiddersTable[string(bidderName)]; !present { allBiddersTable[string(bidderName)] = struct{}{} } @@ -213,7 +216,7 @@ func mergeUser(v *openrtb2.User, overrideJSON json.RawMessage) error { } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } @@ -307,7 +310,7 @@ func mergeSite(v *openrtb2.Site, overrideJSON json.RawMessage, bidderName string } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } @@ -424,7 +427,7 @@ func mergeApp(v *openrtb2.App, overrideJSON json.RawMessage) error { } // Merge - if err := json.Unmarshal(overrideJSON, &v); err != nil { + if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { return err } @@ -462,12 +465,14 @@ func buildExtData(data []byte) []byte { // ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) { fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + reqExtPrebid := reqExt.GetPrebid() if reqExtPrebid != nil { for _, bidderConfig := range reqExtPrebid.BidderConfigs { for _, bidder := range bidderConfig.Bidders { - if _, present := fpd[openrtb_ext.BidderName(bidder)]; present { - //if bidder has duplicated config - throw an error + bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder) + + if _, duplicate := fpd[bidderName]; duplicate { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder), } @@ -476,18 +481,12 @@ func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.Bid fpdBidderData := &openrtb_ext.ORTB2{} if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil { - if bidderConfig.Config.ORTB2.Site != nil { - fpdBidderData.Site = bidderConfig.Config.ORTB2.Site - } - if bidderConfig.Config.ORTB2.App != nil { - fpdBidderData.App = bidderConfig.Config.ORTB2.App - } - if bidderConfig.Config.ORTB2.User != nil { - fpdBidderData.User = bidderConfig.Config.ORTB2.User - } + fpdBidderData.Site = bidderConfig.Config.ORTB2.Site + fpdBidderData.App = bidderConfig.Config.ORTB2.App + fpdBidderData.User = bidderConfig.Config.ORTB2.User } - fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData + fpd[bidderName] = fpdBidderData } } reqExtPrebid.BidderConfigs = nil diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index 4c9cd7ad5e8..aa00c981fa7 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -3,12 +3,14 @@ package firstpartydata import ( "encoding/json" "os" + "path/filepath" "reflect" "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -474,13 +476,13 @@ func TestExtractBidderConfigFPD(t *testing.T) { for _, test := range tests { t.Run(test.Name(), func(t *testing.T) { - filePath := testPath + "/" + test.Name() + path := filepath.Join(testPath, test.Name()) - fpdFile, err := loadFpdFile(filePath) - require.NoError(t, err, "Cannot Load Test") + testFile, err := loadTestFile[fpdFile](path) + require.NoError(t, err, "Load Test File") givenRequestExtPrebid := &openrtb_ext.ExtRequestPrebid{} - err = json.Unmarshal(fpdFile.InputRequestData, givenRequestExtPrebid) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, givenRequestExtPrebid) require.NoError(t, err, "Cannot Load Test Conditions") testRequest := &openrtb_ext.RequestExt{} @@ -490,15 +492,17 @@ func TestExtractBidderConfigFPD(t *testing.T) { results, err := ExtractBidderConfigFPD(testRequest) // assert errors - if len(fpdFile.ValidationErrors) > 0 { - require.EqualError(t, err, fpdFile.ValidationErrors[0].Message, "Expected Error Not Received") + if len(testFile.ValidationErrors) > 0 { + require.EqualError(t, err, testFile.ValidationErrors[0].Message, "Expected Error Not Received") } else { require.NoError(t, err, "Error Not Expected") assert.Nil(t, testRequest.GetPrebid().BidderConfigs, "Bidder specific FPD config should be removed from request") } // assert fpd (with normalization for nicer looking tests) - for bidderName, expectedFPD := range fpdFile.BidderConfigFPD { + for bidderName, expectedFPD := range testFile.BidderConfigFPD { + require.Contains(t, results, bidderName) + if expectedFPD.App != nil { assert.JSONEq(t, string(expectedFPD.App), string(results[bidderName].App), "app is incorrect") } else { @@ -520,7 +524,6 @@ func TestExtractBidderConfigFPD(t *testing.T) { }) } } - func TestResolveFPD(t *testing.T) { testPath := "tests/resolvefpd" @@ -529,141 +532,137 @@ func TestResolveFPD(t *testing.T) { for _, test := range tests { t.Run(test.Name(), func(t *testing.T) { - filePath := testPath + "/" + test.Name() + path := filepath.Join(testPath, test.Name()) - fpdFile, err := loadFpdFile(filePath) - require.NoError(t, err, "Cannot Load Test") + testFile, err := loadTestFile[fpdFileForResolveFPD](path) + require.NoError(t, err, "Load Test File") request := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, &request) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, &request) require.NoError(t, err, "Cannot Load Request") originalRequest := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, &originalRequest) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, &originalRequest) require.NoError(t, err, "Cannot Load Request") - outputReq := &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.OutputRequestData, &outputReq) - require.NoError(t, err, "Cannot Load Output Request") - reqExtFPD := make(map[string][]byte) - reqExtFPD["site"] = fpdFile.GlobalFPD["site"] - reqExtFPD["app"] = fpdFile.GlobalFPD["app"] - reqExtFPD["user"] = fpdFile.GlobalFPD["user"] + reqExtFPD["site"] = testFile.GlobalFPD["site"] + reqExtFPD["app"] = testFile.GlobalFPD["app"] + reqExtFPD["user"] = testFile.GlobalFPD["user"] reqFPD := make(map[string][]openrtb2.Data, 3) - reqFPDSiteContentData := fpdFile.GlobalFPD[siteContentDataKey] + reqFPDSiteContentData := testFile.GlobalFPD[siteContentDataKey] if len(reqFPDSiteContentData) > 0 { var siteConData []openrtb2.Data - err = json.Unmarshal(reqFPDSiteContentData, &siteConData) + err = jsonutil.UnmarshalValid(reqFPDSiteContentData, &siteConData) if err != nil { t.Errorf("Unable to unmarshal site.content.data:") } reqFPD[siteContentDataKey] = siteConData } - reqFPDAppContentData := fpdFile.GlobalFPD[appContentDataKey] + reqFPDAppContentData := testFile.GlobalFPD[appContentDataKey] if len(reqFPDAppContentData) > 0 { var appConData []openrtb2.Data - err = json.Unmarshal(reqFPDAppContentData, &appConData) + err = jsonutil.UnmarshalValid(reqFPDAppContentData, &appConData) if err != nil { t.Errorf("Unable to unmarshal app.content.data: ") } reqFPD[appContentDataKey] = appConData } - reqFPDUserData := fpdFile.GlobalFPD[userDataKey] + reqFPDUserData := testFile.GlobalFPD[userDataKey] if len(reqFPDUserData) > 0 { var userData []openrtb2.Data - err = json.Unmarshal(reqFPDUserData, &userData) + err = jsonutil.UnmarshalValid(reqFPDUserData, &userData) if err != nil { t.Errorf("Unable to unmarshal app.content.data: ") } reqFPD[userDataKey] = userData } - if fpdFile.BidderConfigFPD == nil { - fpdFile.BidderConfigFPD = make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) - fpdFile.BidderConfigFPD["appnexus"] = &openrtb_ext.ORTB2{} - } // run test - resultFPD, errL := ResolveFPD(request, fpdFile.BidderConfigFPD, reqExtFPD, reqFPD, []string{"appnexus"}) + resultFPD, errL := ResolveFPD(request, testFile.BidderConfigFPD, reqExtFPD, reqFPD, testFile.BiddersWithGlobalFPD) if len(errL) == 0 { assert.Equal(t, request, originalRequest, "Original request should not be modified") - bidderFPD := resultFPD["appnexus"] - - if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { - resSiteExt := bidderFPD.Site.Ext - expectedSiteExt := outputReq.Site.Ext - bidderFPD.Site.Ext = nil - outputReq.Site.Ext = nil - assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") - - assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + expectedResultKeys := []string{} + for k := range testFile.OutputRequestData { + expectedResultKeys = append(expectedResultKeys, k.String()) } - if outputReq.App != nil && len(outputReq.App.Ext) > 0 { - resAppExt := bidderFPD.App.Ext - expectedAppExt := outputReq.App.Ext - bidderFPD.App.Ext = nil - outputReq.App.Ext = nil - - assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") - - assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + actualResultKeys := []string{} + for k := range resultFPD { + actualResultKeys = append(actualResultKeys, k.String()) } - if outputReq.User != nil && len(outputReq.User.Ext) > 0 { - resUserExt := bidderFPD.User.Ext - expectedUserExt := outputReq.User.Ext - bidderFPD.User.Ext = nil - outputReq.User.Ext = nil - assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") - - assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + require.ElementsMatch(t, expectedResultKeys, actualResultKeys) + + for k, outputReq := range testFile.OutputRequestData { + bidderFPD := resultFPD[k] + + if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { + resSiteExt := bidderFPD.Site.Ext + expectedSiteExt := outputReq.Site.Ext + bidderFPD.Site.Ext = nil + outputReq.Site.Ext = nil + assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") + assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + } + if outputReq.App != nil && len(outputReq.App.Ext) > 0 { + resAppExt := bidderFPD.App.Ext + expectedAppExt := outputReq.App.Ext + bidderFPD.App.Ext = nil + outputReq.App.Ext = nil + assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") + assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + } + if outputReq.User != nil && len(outputReq.User.Ext) > 0 { + resUserExt := bidderFPD.User.Ext + expectedUserExt := outputReq.User.Ext + bidderFPD.User.Ext = nil + outputReq.User.Ext = nil + assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") + assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + } } } else { - assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect first party data warning message") + assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect first party data warning message") } }) } } - func TestExtractFPDForBidders(t *testing.T) { if specFiles, err := os.ReadDir("./tests/extractfpdforbidders"); err == nil { for _, specFile := range specFiles { - fileName := "./tests/extractfpdforbidders/" + specFile.Name() - - fpdFile, err := loadFpdFile(fileName) + path := filepath.Join("./tests/extractfpdforbidders/", specFile.Name()) - if err != nil { - t.Errorf("Unable to load file: %s", fileName) - } + testFile, err := loadTestFile[fpdFile](path) + require.NoError(t, err, "Load Test File") var expectedRequest openrtb2.BidRequest - err = json.Unmarshal(fpdFile.OutputRequestData, &expectedRequest) + err = jsonutil.UnmarshalValid(testFile.OutputRequestData, &expectedRequest) if err != nil { - t.Errorf("Unable to unmarshal input request: %s", fileName) + t.Errorf("Unable to unmarshal input request: %s", path) } resultRequest := &openrtb_ext.RequestWrapper{} resultRequest.BidRequest = &openrtb2.BidRequest{} - err = json.Unmarshal(fpdFile.InputRequestData, resultRequest.BidRequest) + err = jsonutil.UnmarshalValid(testFile.InputRequestData, resultRequest.BidRequest) assert.NoError(t, err, "Error should be nil") resultFPD, errL := ExtractFPDForBidders(resultRequest) - if len(fpdFile.ValidationErrors) > 0 { - assert.Equal(t, len(fpdFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") - assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect errors were returned") + if len(testFile.ValidationErrors) > 0 { + assert.Equal(t, len(testFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") + assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect errors were returned") //in case or error no further assertions needed continue } assert.Empty(t, errL, "Error should be empty") - assert.Equal(t, len(resultFPD), len(fpdFile.BiddersFPDResolved)) + assert.Equal(t, len(resultFPD), len(testFile.BiddersFPDResolved)) - for bidderName, expectedValue := range fpdFile.BiddersFPDResolved { + for bidderName, expectedValue := range testFile.BiddersFPDResolved { actualValue := resultFPD[bidderName] if expectedValue.Site != nil { if len(expectedValue.Site.Ext) > 0 { @@ -715,34 +714,10 @@ func TestExtractFPDForBidders(t *testing.T) { } assert.Equal(t, expectedRequest.User, resultRequest.BidRequest.User, "Incorrect user in request") } - } } } -func loadFpdFile(filename string) (fpdFile, error) { - var fileData fpdFile - fileContents, err := os.ReadFile(filename) - if err != nil { - return fileData, err - } - err = json.Unmarshal(fileContents, &fileData) - if err != nil { - return fileData, err - } - - return fileData, nil -} - -type fpdFile struct { - InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` - OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` - BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` - BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` - GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` - ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` -} - func TestResolveUser(t *testing.T) { testCases := []struct { description string @@ -751,7 +726,7 @@ func TestResolveUser(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedUser *openrtb2.User - expectedError string + expectError bool }{ { description: "FPD config and bid request user are not specified", @@ -794,7 +769,7 @@ func TestResolveUser(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, globalFPD: map[string][]byte{userKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd user are specified, no input user ext", @@ -863,18 +838,18 @@ func TestResolveUser(t *testing.T) { }, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultUser, err := resolveUser(test.fpdConfig, test.bidRequestUser, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { + if test.expectError { + assert.Error(t, err, "expected error incorrect") + } else { assert.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedUser, resultUser, "Result user is incorrect") - } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") } }) } @@ -888,16 +863,16 @@ func TestResolveSite(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedSite *openrtb2.Site - expectedError string + expectError bool }{ { description: "FPD config and bid request site are not specified", expectedSite: nil, }, { - description: "FPD config site only is specified", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test"}`)}, - expectedError: "incorrect First Party Data for bidder bidderA: Site object is not defined in request, but defined in FPD config", + description: "FPD config site only is specified", + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test"}`)}, + expectError: true, }, { description: "FPD config and bid request site are specified", @@ -931,7 +906,7 @@ func TestResolveSite(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, globalFPD: map[string][]byte{siteKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd site are specified, no input site ext", @@ -1023,18 +998,18 @@ func TestResolveSite(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultSite, err := resolveSite(test.fpdConfig, test.bidRequestSite, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { + if test.expectError { + assert.Error(t, err) + } else { assert.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedSite, resultSite, "Result site is incorrect") - } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") } }) } @@ -1048,16 +1023,16 @@ func TestResolveApp(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedApp *openrtb2.App - expectedError string + expectError bool }{ { description: "FPD config and bid request app are not specified", expectedApp: nil, }, { - description: "FPD config app only is specified", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test"}`)}, - expectedError: "incorrect First Party Data for bidder bidderA: App object is not defined in request, but defined in FPD config", + description: "FPD config app only is specified", + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test"}`)}, + expectError: true, }, { description: "FPD config and bid request app are specified", @@ -1091,7 +1066,7 @@ func TestResolveApp(t *testing.T) { fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, globalFPD: map[string][]byte{appKey: []byte(`malformed`)}, - expectedError: "Invalid JSON Patch", + expectError: true, }, { description: "bid request and openrtb global fpd app are specified, no input app ext", @@ -1183,18 +1158,18 @@ func TestResolveApp(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectedError: "invalid character 'm' looking for beginning of object key string", + expectError: true, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultApp, err := resolveApp(test.fpdConfig, test.bidRequestApp, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectedError == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedError, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect") } }) } @@ -1245,7 +1220,7 @@ func TestMergeUser(t *testing.T) { givenUser openrtb2.User givenFPD json.RawMessage expectedUser openrtb2.User - expectedErr string + expectError bool }{ { name: "empty", @@ -1269,7 +1244,7 @@ func TestMergeUser(t *testing.T) { name: "toplevel-ext-err", givenUser: openrtb2.User{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-geo", @@ -1293,13 +1268,13 @@ func TestMergeUser(t *testing.T) { name: "nested-geo-ext-err", givenUser: openrtb2.User{Geo: &openrtb2.Geo{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"geo":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenUser: openrtb2.User{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1307,11 +1282,11 @@ func TestMergeUser(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeUser(&test.givenUser, test.givenFPD) - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedUser, test.givenUser, "result user is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedUser, test.givenUser, "result user is incorrect") } }) } @@ -1323,7 +1298,7 @@ func TestMergeApp(t *testing.T) { givenApp openrtb2.App givenFPD json.RawMessage expectedApp openrtb2.App - expectedErr string + expectError bool }{ { name: "empty", @@ -1347,7 +1322,7 @@ func TestMergeApp(t *testing.T) { name: "toplevel-ext-err", givenApp: openrtb2.App{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-publisher", @@ -1443,37 +1418,37 @@ func TestMergeApp(t *testing.T) { name: "nested-publisher-ext-err", givenApp: openrtb2.App{Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-producer-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-network-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-channel-ext-err", givenApp: openrtb2.App{Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenApp: openrtb2.App{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1481,11 +1456,11 @@ func TestMergeApp(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeApp(&test.givenApp, test.givenFPD) - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedApp, test.givenApp, " result app is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedApp, test.givenApp, " result app is incorrect") } }) } @@ -1497,13 +1472,13 @@ func TestMergeSite(t *testing.T) { givenSite openrtb2.Site givenFPD json.RawMessage expectedSite openrtb2.Site - expectedErr string + expectError bool }{ { name: "empty", givenSite: openrtb2.Site{}, givenFPD: []byte(`{}`), - expectedErr: "incorrect First Party Data for bidder BidderA: Site object cannot set empty page if req.site.id is empty", + expectError: true, }, { name: "toplevel", @@ -1521,7 +1496,7 @@ func TestMergeSite(t *testing.T) { name: "toplevel-ext-err", givenSite: openrtb2.Site{ID: "1", Ext: []byte(`malformed`)}, givenFPD: []byte(`{"id":"2"}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-publisher", @@ -1617,37 +1592,37 @@ func TestMergeSite(t *testing.T) { name: "nested-publisher-ext-err", givenSite: openrtb2.Site{ID: "1", Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-producer-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-network-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "nested-content-channel-ext-err", givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectedErr: "invalid request ext", + expectError: true, }, { name: "fpd-err", givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1}`)}, givenFPD: []byte(`malformed`), - expectedErr: "invalid character 'm' looking for beginning of value", + expectError: true, }, } @@ -1655,11 +1630,11 @@ func TestMergeSite(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mergeSite(&test.givenSite, test.givenFPD, "BidderA") - if test.expectedErr == "" { - assert.NoError(t, err, "unexpected error returned") - assert.Equal(t, test.expectedSite, test.givenSite, " result Site is incorrect") + if test.expectError { + assert.Error(t, err) } else { - assert.EqualError(t, err, test.expectedErr, "expected error incorrect") + assert.NoError(t, err) + assert.Equal(t, test.expectedSite, test.givenSite, " result Site is incorrect") } }) } @@ -1927,3 +1902,37 @@ var ( } `) ) + +func loadTestFile[T any](filename string) (T, error) { + var testFile T + + b, err := os.ReadFile(filename) + if err != nil { + return testFile, err + } + + err = json.Unmarshal(b, &testFile) + if err != nil { + return testFile, err + } + + return testFile, nil +} + +type fpdFile struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} + +type fpdFileForResolveFPD struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData map[openrtb_ext.BidderName]openrtb2.BidRequest `json:"outputRequestData,omitempty"` + BiddersWithGlobalFPD []string `json:"biddersWithGlobalFPD,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json new file mode 100644 index 00000000000..934afe47de0 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-case-normalize.json @@ -0,0 +1,28 @@ +{ + "description": "Extracts bidder configs for a bidder, normalizing the case for a known bidder", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "APPNexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json new file mode 100644 index 00000000000..ed9904579d1 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated-case-insensitive.json @@ -0,0 +1,63 @@ +{ + "description": "Verifies error presence in case more than one bidder config specified for the same bidder, case insensitive", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "APPNEXUS", + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": {}, + "validationErrors": [ + { + "Message": "multiple First Party Data bidder configs provided for bidder: APPNEXUS" + } + ] +} \ No newline at end of file diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json new file mode 100644 index 00000000000..1becf730ada --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-request-alias-case-sensitive.json @@ -0,0 +1,28 @@ +{ + "description": "Extracts bidder configs for a bidder, normalizing the case", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "requestAlias" + ], + "config": { + "ortb2": { + "site": { + "id": "aliasSiteId" + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": { + "requestAlias": { + "site": { + "id": "aliasSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json index 797846ee7a6..6e199f4eb0f 100644 --- a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json @@ -84,4 +84,4 @@ } }, "validationErrors": [] -} +} \ No newline at end of file diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json new file mode 100644 index 00000000000..074c8ddef4e --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-case-insensitive-integration.json @@ -0,0 +1,88 @@ +{ + "description": "case insensitive known bidder, case sensitive request alias", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "APPNEXUS", + "requestAlias" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "requestAlias" + ], + "config": { + "ortb2": { + "user": { + "keywords": "aliasUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "apnUserId" + } + }, + "requestAlias": { + "user": { + "id": "reqUserId", + "keywords": "aliasUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json new file mode 100644 index 00000000000..a9062d7ac4b --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-matches.json @@ -0,0 +1,25 @@ +{ + "description": "Bidder FPD defined with a case sensitive request alias, positive test", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "requestAlias" + ], + "bidderConfigFPD": { + "requestAlias": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "requestAlias": { + "app": { + "id": "apnAppId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json new file mode 100644 index 00000000000..ffc549c3cef --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-alias-not-matches.json @@ -0,0 +1,21 @@ +{ + "description": "Bidder FPD defined with a case sensitive request alias, negative test", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "requestAlias" + ], + "bidderConfigFPD": { + "REQUESTALIAS": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "requestAlias": {} + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json b/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json new file mode 100644 index 00000000000..1f3e93b153c --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-case-normalize.json @@ -0,0 +1,25 @@ +{ + "description": "Bidder FPD defined with a case insensitive bidder", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "APPNEXUS" + ], + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId" + } + } + }, + "outputRequestData": { + "appnexus": { + "app": { + "id": "apnAppId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json index 812db7b10b5..7dc9adee182 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json @@ -1,30 +1,35 @@ { - "description": "Bidder FPD defined only for app", - "inputRequestData": { - "app": { - "id": "reqUserID" - } - }, - "bidderConfigFPD": { - "appnexus": { - "app": { - "id": "apnAppId", - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for app", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "app": { - "id": "apnAppId", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json index 5accfc3b3a0..8ee3b40175b 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json @@ -1,30 +1,35 @@ { - "description": "Bidder FPD defined only for site", - "inputRequestData": { - "site": { - "id": "reqUserID" - } - }, - "bidderConfigFPD": { - "appnexus": { - "site": { - "id": "apnSiteId", - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for site", + "inputRequestData": { + "site": { + "id": "reqUserID" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "site": { - "id": "apnSiteId", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json index 606cee7dbe6..204cf3c8d3f 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json @@ -1,35 +1,40 @@ { - "description": "Bidder FPD defined only for user", - "inputRequestData": { - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M" - } - }, - "bidderConfigFPD": { - "appnexus": { - "user": { - "id": "apnUserId", - "yob": 1982, - "ext": { - "data": { - "other": "data" - } - } - } + "description": "Bidder FPD defined only for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "other": "data" + } } - }, - "outputRequestData": { - "user": { - "id": "apnUserId", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "other": "data" - } - } + } + } + }, + "outputRequestData": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "other": "data" + } } + } } + } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json index 541d51f42af..2008d46f265 100644 --- a/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-site-content-data-only.json @@ -28,6 +28,9 @@ "ifa": "123" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -60,44 +63,46 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "name": "apnSiteName", - "domain": "apnSiteDomain", - "page": "http://www.foobar.com/1234.html", - "cat": [ - "books", - "novels" - ], - "search": "book search", - "publisher": { - "id": "1" - }, - "content": { - "episode": 7, - "title": "apnEpisodeName", - "series": "TvName", - "season": "season3", - "len": 600, - "data": [ - { - "id": "siteData3", - "name": "siteName3" + "appnexus": { + "site": { + "id": "apnSiteId", + "name": "apnSiteName", + "domain": "apnSiteDomain", + "page": "http://www.foobar.com/1234.html", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 7, + "title": "apnEpisodeName", + "series": "TvName", + "season": "season3", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "ext": { + "data": { + "other": "data", + "testSiteFpd": "testSite" } - ] - }, - "ext": { - "data": { - "other": "data", - "testSiteFpd": "testSite" } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" } } } \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json index 16b5332e942..7be277eb5ff 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json @@ -14,6 +14,9 @@ "gender": "reqUserGender" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -55,48 +58,50 @@ ] }, "outputRequestData": { - "app": { - "id": "apnAppId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" + "appnexus": { + "app": { + "id": "apnAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpd": 123 + } + } }, - "content": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "reqUserGender", "data": [ { - "id": "appData1", - "name": "appName1" + "id": "userData1", + "name": "userName1" }, { - "id": "appData2", - "name": "appName2" + "id": "userData2", + "name": "userName2" + } + ], + "ext": { + "data": { + "testUserFpd": "testuser" } - ] - }, - "ext": { - "data": { - "morefpdData": "morefpddata", - "appFpd": 123 - } - } - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "reqUserGender", - "data": [ - { - "id": "userData1", - "name": "userName1" - }, - { - "id": "userData2", - "name": "userName2" - } - ], - "ext": { - "data": { - "testUserFpd": "testuser" } } } diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json index 609ede597cf..fb27d1631c1 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json @@ -22,6 +22,9 @@ "ifa": "123" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -44,36 +47,37 @@ } }, "outputRequestData": { - "app": { - "id": "apnAppId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "appFpd": 123, - "appFpddata": "appFpddata" + "appnexus": { + "app": { + "id": "apnAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json index f66a8722308..9563fffebcc 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json @@ -19,6 +19,9 @@ "id": "apnUserId" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -51,40 +54,41 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "morefpdData": "morefpddata", - "siteFpddata": "siteFpddata", - "moreFpd": { - "fpd": 123 + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } } } - } - }, - "user": { - "id": "apnUserId", - "ext": { - "data": { - "moreFpd": { - "fpd": 567 - }, - "testUserFpd": "testuser" + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "moreFpd": { + "fpd": 567 + }, + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json index be03f0cb9b0..4362362718a 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json @@ -16,6 +16,9 @@ } } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -40,31 +43,32 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900 - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json index ef381d613e6..4d233e6c473 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json @@ -7,6 +7,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "user": { @@ -30,21 +33,22 @@ } }, "outputRequestData": { - "user": { - "id": "apnUserId", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser", - "morefpdData": "morefpddata", - "userFpddata": "siteFpddata", - "moreFpd": { - "fpd": 123 + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser", + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json index c0f4723d62a..a0a1c461292 100644 --- a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json @@ -20,6 +20,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -62,52 +65,53 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json index a705e8b2405..e8dace465e7 100644 --- a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-app-user.json @@ -23,6 +23,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -72,58 +75,59 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "title": "episodeName8", - "series": "TvName", - "season": "season4", - "episode": 8, - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "episode": 8, + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json index ccfa030cd8d..10fa471744f 100644 --- a/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json +++ b/firstpartydata/tests/resolvefpd/global-and-no-bidder-fpd-site-content-app-user.json @@ -23,6 +23,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -65,53 +68,54 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-app.json b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json index e4d5e169986..55839983a5b 100644 --- a/firstpartydata/tests/resolvefpd/global-fpd-only-app.json +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-app.json @@ -29,6 +29,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -64,56 +67,57 @@ ] }, "outputRequestData": { - "app": { - "id": "apnAppId", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "data": [ - { - "id": "appData1", - "name": "appName1" - }, - { - "id": "appData2", - "name": "appName2" + "appnexus": { + "app": { + "id": "apnAppId", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "testAppExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" } - ] - }, - "ext": { - "testAppExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "appFpd": 123, - "appFpddata": "appFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json b/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json new file mode 100644 index 00000000000..b23a347cf9b --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-excludes-some.json @@ -0,0 +1,138 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Excludes rubicon since not listed in biddersWithGlobalFPD", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json new file mode 100644 index 00000000000..f959ebcde4f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-explicit.json @@ -0,0 +1,144 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Bidders mentioned explicitly in biddersWithGlobalFPD", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": [ + "appnexus", + "rubicon" + ], + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json new file mode 100644 index 00000000000..7c1f0f10aff --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-include-all-implicit.json @@ -0,0 +1,141 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data. Bidders included implicitly with biddersWithGlobalFPD nil", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "biddersWithGlobalFPD": null, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + }, + "rubicon": { + "site": { + "id": "rbcSiteId" + } + } + } +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/global-fpd-only-site.json b/firstpartydata/tests/resolvefpd/global-fpd-only-site.json index 49b8f4dcefb..3f7e91adc63 100644 --- a/firstpartydata/tests/resolvefpd/global-fpd-only-site.json +++ b/firstpartydata/tests/resolvefpd/global-fpd-only-site.json @@ -30,6 +30,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -72,58 +75,59 @@ ] }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 6, - "title": "episodeName", - "series": "TvName", - "season": "season3", - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json index 446e7151afa..da0d4357dd1 100644 --- a/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json +++ b/firstpartydata/tests/resolvefpd/req-and-bidder-fpd-site-content.json @@ -40,6 +40,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -89,58 +92,59 @@ } }, "outputRequestData": { - "site": { - "id": "apnSiteId", - "page": "http://www.foobar.com/1234.html", - "ref": "fpdRef", - "publisher": { - "id": "1" - }, - "content": { - "episode": 8, - "title": "episodeName8", - "series": "TvName", - "season": "season4", - "len": 900, - "data": [ - { - "id": "siteData1", - "name": "siteName1" - }, - { - "id": "siteData2", - "name": "siteName2" + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 8, + "title": "episodeName8", + "series": "TvName", + "season": "season4", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" } - ] - }, - "ext": { - "testSiteExt": 123, - "data": { - "moreFpd": { - "fpd": 123 - }, - "morefpdData": "morefpddata", - "siteFpd": 123, - "siteFpddata": "siteFpddata" } - } - }, - "device": { - "ua": "testDevice", - "ip": "123.145.167.10", - "devicetype": 1, - "ifa": "123" - }, - "user": { - "id": "reqUserID", - "yob": 1982, - "gender": "M", - "ext": { - "data": { - "testUserFpd": "testuser" + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } } } } } -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-app-not-defined.json b/firstpartydata/tests/resolvefpd/req-app-not-defined.json index f24a4d27db1..6ab9e076f43 100644 --- a/firstpartydata/tests/resolvefpd/req-app-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-app-not-defined.json @@ -20,6 +20,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "app": { @@ -42,5 +45,4 @@ "Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-site-not-defined.json b/firstpartydata/tests/resolvefpd/req-site-not-defined.json index d078ad7804f..8e20a6e0e1d 100644 --- a/firstpartydata/tests/resolvefpd/req-site-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-site-not-defined.json @@ -7,6 +7,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -29,5 +32,4 @@ "Message": "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/req-user-not-defined.json b/firstpartydata/tests/resolvefpd/req-user-not-defined.json index 76ae3f827ca..85c41c23969 100644 --- a/firstpartydata/tests/resolvefpd/req-user-not-defined.json +++ b/firstpartydata/tests/resolvefpd/req-user-not-defined.json @@ -5,6 +5,9 @@ "at": 1, "tmax": 5000 }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "user": { @@ -21,11 +24,12 @@ } } }, - "outputRequestData": {}, + "outputRequestData": { + "appnexus": {} + }, "validationErrors": [ { "Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config" } ] -} - +} \ No newline at end of file diff --git a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json index 8da3dc69329..260385c49be 100644 --- a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json +++ b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json @@ -19,6 +19,9 @@ "gender": "M" } }, + "biddersWithGlobalFPD": [ + "appnexus" + ], "bidderConfigFPD": { "appnexus": { "site": { @@ -41,5 +44,4 @@ "Message": "incorrect First Party Data for bidder appnexus: Site object cannot set empty page if req.site.id is empty" } ] -} - +} \ No newline at end of file diff --git a/floors/enforce.go b/floors/enforce.go index 1f50dd2b1d9..e1f4d8d015b 100644 --- a/floors/enforce.go +++ b/floors/enforce.go @@ -6,10 +6,10 @@ import ( "math/rand" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Enforce does floors enforcement for bids from all bidders based on floors provided in request, account level floors config @@ -137,7 +137,7 @@ func enforceFloorToBids(bidRequestWrapper *openrtb_ext.RequestWrapper, seatBids } bidPrice := rate * bid.Bid.Price - if reqImp.BidFloor > bidPrice { + if (bidPrice + floorPrecision) < reqImp.BidFloor { rejectedBid := &entities.PbsOrtbSeatBid{ Currency: seatBid.Currency, Seat: seatBid.Seat, diff --git a/floors/enforce_test.go b/floors/enforce_test.go index bb5d44341ef..085506c3411 100644 --- a/floors/enforce_test.go +++ b/floors/enforce_test.go @@ -7,11 +7,11 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -196,6 +196,71 @@ func TestEnforceFloorToBids(t *testing.T) { }, expErrs: []error{}, }, + { + name: "Bids with price less than bidfloor with floorsPrecision", + args: args{ + bidRequestWrapper: func() *openrtb_ext.RequestWrapper { + bw := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{ + {ID: "some-impression-id-1", BidFloor: 1, BidFloorCur: "USD"}, + {ID: "some-impression-id-2", BidFloor: 2, BidFloorCur: "USD"}, + }, + }, + } + bw.RebuildRequest() + return &bw + }(), + seatBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 0.998, ImpID: "some-impression-id-1"}}, + {Bid: &openrtb2.Bid{ID: "some-bid-2", Price: 1.5, DealID: "deal_Id", ImpID: "some-impression-id-2"}}, + }, + Seat: "pubmatic", + Currency: "USD", + }, + "appnexus": { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 0.8, ImpID: "some-impression-id-1"}}, + {Bid: &openrtb2.Bid{ID: "some-bid-12", Price: 2.2, ImpID: "some-impression-id-2"}}, + }, + Seat: "appnexus", + Currency: "USD", + }, + }, + conversions: currency.Conversions(convert{}), + enforceDealFloors: false, + }, + expEligibleBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "some-bid-1", Price: 0.998, ImpID: "some-impression-id-1"}}, + {Bid: &openrtb2.Bid{ID: "some-bid-2", Price: 1.5, DealID: "deal_Id", ImpID: "some-impression-id-2"}}, + }, + Seat: "pubmatic", + Currency: "USD", + }, + "appnexus": { + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "some-bid-12", Price: 2.2, ImpID: "some-impression-id-2"}}, + }, + Seat: "appnexus", + Currency: "USD", + }, + }, + expRejectedBids: []*entities.PbsOrtbSeatBid{ + { + Seat: "appnexus", + Currency: "USD", + Bids: []*entities.PbsOrtbBid{ + {Bid: &openrtb2.Bid{ID: "some-bid-11", Price: 0.8, ImpID: "some-impression-id-1"}}, + }, + }, + }, + expErrs: []error{}, + }, { name: "Bids with different currency with enforceDealFloor true", args: args{ @@ -1154,7 +1219,6 @@ func TestUpdateEnforcePBS(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := updateEnforcePBS(tt.args.enforceFloors, tt.args.reqExt) assert.Equal(t, tt.want, got, tt.name) - }) } } diff --git a/floors/fetcher.go b/floors/fetcher.go new file mode 100644 index 00000000000..6df43445010 --- /dev/null +++ b/floors/fetcher.go @@ -0,0 +1,331 @@ +package floors + +import ( + "container/heap" + "context" + "encoding/json" + "errors" + "io" + "math" + "net/http" + "strconv" + "time" + + "github.com/alitto/pond" + validator "github.com/asaskevich/govalidator" + "github.com/coocood/freecache" + "github.com/golang/glog" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" +) + +var refetchCheckInterval = 300 + +type fetchInfo struct { + config.AccountFloorFetch + fetchTime int64 + refetchRequest bool + retryCount int +} + +type WorkerPool interface { + TrySubmit(task func()) bool + Stop() +} + +type FloorFetcher interface { + Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) + Stop() +} + +type PriceFloorFetcher struct { + pool WorkerPool // Goroutines worker pool + fetchQueue FetchQueue // Priority Queue to fetch floor data + fetchInProgress map[string]bool // Map of URL with fetch status + configReceiver chan fetchInfo // Channel which recieves URLs to be fetched + done chan struct{} // Channel to close fetcher + cache *freecache.Cache // cache + httpClient *http.Client // http client to fetch data from url + time timeutil.Time // time interface to record request timings + metricEngine metrics.MetricsEngine // Records malfunctions in dynamic fetch + maxRetries int // Max number of retries for failing URLs +} + +type FetchQueue []*fetchInfo + +func (fq FetchQueue) Len() int { + return len(fq) +} + +func (fq FetchQueue) Less(i, j int) bool { + return fq[i].fetchTime < fq[j].fetchTime +} + +func (fq FetchQueue) Swap(i, j int) { + fq[i], fq[j] = fq[j], fq[i] +} + +func (fq *FetchQueue) Push(element interface{}) { + fetchInfo := element.(*fetchInfo) + *fq = append(*fq, fetchInfo) +} + +func (fq *FetchQueue) Pop() interface{} { + old := *fq + n := len(old) + fetchInfo := old[n-1] + old[n-1] = nil + *fq = old[0 : n-1] + return fetchInfo +} + +func (fq *FetchQueue) Top() *fetchInfo { + old := *fq + if len(old) == 0 { + return nil + } + return old[0] +} + +func workerPanicHandler(p interface{}) { + glog.Errorf("floor fetcher worker panicked: %v", p) +} + +func NewPriceFloorFetcher(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: config.Fetcher.MaxRetries, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func (f *PriceFloorFetcher) SetWithExpiry(key string, value json.RawMessage, cacheExpiry int) { + f.cache.Set([]byte(key), value, cacheExpiry) +} + +func (f *PriceFloorFetcher) Get(key string) (json.RawMessage, bool) { + data, err := f.cache.Get([]byte(key)) + if err != nil { + return nil, false + } + + return data, true +} + +func (f *PriceFloorFetcher) Fetch(config config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + if f == nil || !config.UseDynamicData || len(config.Fetcher.URL) == 0 || !validator.IsURL(config.Fetcher.URL) { + return nil, openrtb_ext.FetchNone + } + + // Check for floors JSON in cache + if result, found := f.Get(config.Fetcher.URL); found { + var fetchedFloorData openrtb_ext.PriceFloorRules + if err := json.Unmarshal(result, &fetchedFloorData); err != nil || fetchedFloorData.Data == nil { + return nil, openrtb_ext.FetchError + } + return &fetchedFloorData, openrtb_ext.FetchSuccess + } + + //miss: push to channel to fetch and return empty response + if config.Enabled && config.Fetcher.Enabled && config.Fetcher.Timeout > 0 { + fetchConfig := fetchInfo{AccountFloorFetch: config.Fetcher, fetchTime: f.time.Now().Unix(), refetchRequest: false, retryCount: 0} + f.configReceiver <- fetchConfig + } + + return nil, openrtb_ext.FetchInprogress +} + +func (f *PriceFloorFetcher) worker(fetchConfig fetchInfo) { + floorData, fetchedMaxAge := f.fetchAndValidate(fetchConfig.AccountFloorFetch) + if floorData != nil { + // Reset retry count when data is successfully fetched + fetchConfig.retryCount = 0 + + // Update cache with new floor rules + cacheExpiry := fetchConfig.AccountFloorFetch.MaxAge + if fetchedMaxAge != 0 { + cacheExpiry = fetchedMaxAge + } + floorData, err := json.Marshal(floorData) + if err != nil { + glog.Errorf("Error while marshaling fetched floor data for url %s", fetchConfig.AccountFloorFetch.URL) + } else { + f.SetWithExpiry(fetchConfig.AccountFloorFetch.URL, floorData, cacheExpiry) + } + } else { + fetchConfig.retryCount++ + } + + // Send to refetch channel + if fetchConfig.retryCount < f.maxRetries { + fetchConfig.fetchTime = f.time.Now().Add(time.Duration(fetchConfig.AccountFloorFetch.Period) * time.Second).Unix() + fetchConfig.refetchRequest = true + f.configReceiver <- fetchConfig + } +} + +// Stop terminates price floor fetcher +func (f *PriceFloorFetcher) Stop() { + if f == nil { + return + } + + close(f.done) + f.pool.Stop() + close(f.configReceiver) +} + +func (f *PriceFloorFetcher) submit(fetchConfig *fetchInfo) { + status := f.pool.TrySubmit(func() { + f.worker(*fetchConfig) + }) + if !status { + heap.Push(&f.fetchQueue, fetchConfig) + } +} + +func (f *PriceFloorFetcher) Fetcher() { + //Create Ticker of 5 minutes + ticker := time.NewTicker(time.Duration(refetchCheckInterval) * time.Second) + + for { + select { + case fetchConfig := <-f.configReceiver: + if fetchConfig.refetchRequest { + heap.Push(&f.fetchQueue, &fetchConfig) + } else { + if _, ok := f.fetchInProgress[fetchConfig.URL]; !ok { + f.fetchInProgress[fetchConfig.URL] = true + f.submit(&fetchConfig) + } + } + case <-ticker.C: + currentTime := f.time.Now().Unix() + for top := f.fetchQueue.Top(); top != nil && top.fetchTime <= currentTime; top = f.fetchQueue.Top() { + nextFetch := heap.Pop(&f.fetchQueue) + f.submit(nextFetch.(*fetchInfo)) + } + case <-f.done: + ticker.Stop() + glog.Info("Price Floor fetcher terminated") + return + } + } +} + +func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (*openrtb_ext.PriceFloorRules, int) { + floorResp, maxAge, err := f.fetchFloorRulesFromURL(config) + if floorResp == nil || err != nil { + glog.Errorf("Error while fetching floor data from URL: %s, reason : %s", config.URL, err.Error()) + return nil, 0 + } + + if len(floorResp) > (config.MaxFileSizeKB * 1024) { + glog.Errorf("Recieved invalid floor data from URL: %s, reason : floor file size is greater than MaxFileSize", config.URL) + return nil, 0 + } + + var priceFloors openrtb_ext.PriceFloorRules + if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { + glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) + return nil, 0 + } + + if err := validateRules(config, &priceFloors); err != nil { + glog.Errorf("Validation failed for floor JSON from URL: %s, reason: %s", config.URL, err.Error()) + return nil, 0 + } + + return &priceFloors, maxAge +} + +// fetchFloorRulesFromURL returns a price floor JSON and time for which this JSON is valid +// from provided URL with timeout constraints +func (f *PriceFloorFetcher) fetchFloorRulesFromURL(config config.AccountFloorFetch) ([]byte, int, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Timeout)*time.Millisecond) + defer cancel() + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, config.URL, nil) + if err != nil { + return nil, 0, errors.New("error while forming http fetch request : " + err.Error()) + } + + httpResp, err := f.httpClient.Do(httpReq) + if err != nil { + return nil, 0, errors.New("error while getting response from url : " + err.Error()) + } + + if httpResp.StatusCode != http.StatusOK { + return nil, 0, errors.New("no response from server") + } + + var maxAge int + if maxAgeStr := httpResp.Header.Get("max-age"); maxAgeStr != "" { + maxAge, err = strconv.Atoi(maxAgeStr) + if err != nil { + glog.Errorf("max-age in header is malformed for url %s", config.URL) + } + if maxAge <= config.Period || maxAge > math.MaxInt32 { + glog.Errorf("Invalid max-age = %s provided, value should be valid integer and should be within (%v, %v)", maxAgeStr, config.Period, math.MaxInt32) + } + } + + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, 0, errors.New("unable to read response") + } + defer httpResp.Body.Close() + + return respBody, maxAge, nil +} + +func validateRules(config config.AccountFloorFetch, priceFloors *openrtb_ext.PriceFloorRules) error { + if priceFloors.Data == nil { + return errors.New("empty data in floor JSON") + } + + if len(priceFloors.Data.ModelGroups) == 0 { + return errors.New("no model groups found in price floor data") + } + + if priceFloors.Data.SkipRate < 0 || priceFloors.Data.SkipRate > 100 { + return errors.New("skip rate should be greater than or equal to 0 and less than 100") + } + + for _, modelGroup := range priceFloors.Data.ModelGroups { + if len(modelGroup.Values) == 0 || len(modelGroup.Values) > config.MaxRules { + return errors.New("invalid number of floor rules, floor rules should be greater than zero and less than MaxRules specified in account config") + } + + if modelGroup.ModelWeight != nil && (*modelGroup.ModelWeight < 1 || *modelGroup.ModelWeight > 100) { + return errors.New("modelGroup[].modelWeight should be greater than or equal to 1 and less than 100") + } + + if modelGroup.SkipRate < 0 || modelGroup.SkipRate > 100 { + return errors.New("model group skip rate should be greater than or equal to 0 and less than 100") + } + + if modelGroup.Default < 0 { + return errors.New("modelGroup.Default should be greater than 0") + } + } + + return nil +} diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go new file mode 100644 index 00000000000..085fd3edd1b --- /dev/null +++ b/floors/fetcher_test.go @@ -0,0 +1,1279 @@ +package floors + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/alitto/pond" + "github.com/coocood/freecache" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/timeutil" + "github.com/stretchr/testify/assert" +) + +const MaxAge = "max-age" + +func TestFetchQueueLen(t *testing.T) { + tests := []struct { + name string + fq FetchQueue + want int + }{ + { + name: "Queue is empty", + fq: make(FetchQueue, 0), + want: 0, + }, + { + name: "Queue is of lenght 1", + fq: make(FetchQueue, 1), + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Len(); got != tt.want { + t.Errorf("FetchQueue.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueLess(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + want bool + }{ + { + name: "first fetchperiod is less than second", + fq: FetchQueue{&fetchInfo{fetchTime: 10}, &fetchInfo{fetchTime: 20}}, + args: args{i: 0, j: 1}, + want: true, + }, + { + name: "first fetchperiod is greater than second", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Less(tt.args.i, tt.args.j); got != tt.want { + t.Errorf("FetchQueue.Less() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueSwap(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + }{ + { + name: "Swap two elements at index i and j", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fInfo1, fInfo2 := tt.fq[0], tt.fq[1] + tt.fq.Swap(tt.args.i, tt.args.j) + assert.Equal(t, fInfo1, tt.fq[1], "elements are not swapped") + assert.Equal(t, fInfo2, tt.fq[0], "elements are not swapped") + }) + } +} + +func TestFetchQueuePush(t *testing.T) { + type args struct { + element interface{} + } + tests := []struct { + name string + fq *FetchQueue + args args + }{ + { + name: "Push element to queue", + fq: &FetchQueue{}, + args: args{element: &fetchInfo{fetchTime: 10}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fq.Push(tt.args.element) + q := *tt.fq + assert.Equal(t, q[0], &fetchInfo{fetchTime: 10}) + }) + } +} + +func TestFetchQueuePop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want interface{} + }{ + { + name: "Pop element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 10}}, + want: &fetchInfo{fetchTime: 10}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Pop(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Pop() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueTop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want *fetchInfo + }{ + { + name: "Get top element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 20}}, + want: &fetchInfo{fetchTime: 20}, + }, + { + name: "Queue is empty", + fq: &FetchQueue{}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Top(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Top() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidatePriceFloorRules(t *testing.T) { + var zero = 0 + var one_o_one = 101 + var testURL = "abc.com" + type args struct { + configs config.AccountFloorFetch + priceFloors *openrtb_ext.PriceFloorRules + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Price floor data is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{}, + }, + wantErr: true, + }, + { + name: "Model group array is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{}, + }, + }, + wantErr: true, + }, + { + name: "floor rules is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{}, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "floor rules is grater than max floor rules", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 0, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is zero", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &zero, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &one_o_one, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "skiprate is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + SkipRate: 101, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Default is -1", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + Default: -1, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Invalid skip rate in data", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + SkipRate: -44, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateRules(tt.args.configs, tt.args.priceFloors); (err != nil) != tt.wantErr { + t.Errorf("validatePriceFloorRules() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFetchFloorRulesFromURL(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "20") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 20, + wantErr: false, + }, + { + name: "Time out occured", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 0, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "Invalid URL", + args: args{ + configs: config.AccountFloorFetch{ + URL: "%%", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 500, + wantErr: true, + }, + { + name: "Invalid response", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + response: []byte("1"), + responseStatus: 200, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + pff := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := pff.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchFloorRulesFromURLInvalidMaxAge(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "abc") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := ppf.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchAndValidate(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "30") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want *openrtb_ext.PriceFloorRules + want1 int + }{ + { + name: "Recieved valid price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + return []byte(data) + }(), + responseStatus: 200, + want: func() *openrtb_ext.PriceFloorRules { + var res openrtb_ext.PriceFloorRules + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + _ = json.Unmarshal([]byte(data), &res.Data) + return &res + }(), + want1: 30, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: []byte{}, + responseStatus: 500, + want: nil, + want1: 0, + }, + { + name: "File is greater than MaxFileSize", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 1, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","floorProvider":"PM","floorsSchemaVersion":2,"modelGroups":[{"modelVersion":"M_0","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.7}},{"modelVersion":"M_1","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":1.85}},{"modelVersion":"M_2","modelWeight":5,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.6,"www.missyusa.com":0.7}},{"modelVersion":"M_3","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":0.75}},{"modelVersion":"M_4","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.35,"missyusa.com":1.75}},{"modelVersion":"M_5","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":0.9}},{"modelVersion":"M_6","modelWeight":43,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":2}},{"modelVersion":"M_7","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":1.85}},{"modelVersion":"M_8","modelWeight":3,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.7,"missyusa.com":0.1}},{"modelVersion":"M_9","modelWeight":7,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":1.05}},{"modelVersion":"M_10","modelWeight":9,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":0.1}},{"modelVersion":"M_11","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":1.5}},{"modelVersion":"M_12","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.7}},{"modelVersion":"M_13","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.75}},{"modelVersion":"M_14","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.8,"www.missyusa.com":1}},{"modelVersion":"M_15","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.2,"missyusa.com":1.75}},{"modelVersion":"M_16","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":0.7}},{"modelVersion":"M_17","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":0.35}},{"modelVersion":"M_18","modelWeight":3,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.05}}],"skipRate":10}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Malformed response : json unmarshalling failed", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 800, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":nil?}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Validations failed for price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + tt.args.configs.URL = mockHttpServer.URL + got, got1 := ppf.fetchAndValidate(tt.args.configs) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchAndValidate() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchAndValidate() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func mockFetcherInstance(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: 10, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func TestFetcherWhenRequestGetSameURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") + +} + +func TestFetcherDataPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + var res *openrtb_ext.PriceFloorRules + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + _ = json.Unmarshal([]byte(data), &res) + fetcherInstance.SetWithExpiry("http://test.com/floor", []byte(data), fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + assert.Equal(t, res, val, "Invalid value in cache or cache is empty") + assert.Equal(t, "success", status, "Floor fetch should be success") +} + +func TestFetcherDataNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + fetcherInstance.SetWithExpiry("http://test.com/floor", nil, fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, "error", status, "Floor fetch should be error") +} + +func TestFetcherEntryNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 10, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchInprogress, status, "Floor fetch should be error") +} + +func TestFetcherDynamicFetchDisable(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 5, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchNone, status, "Floor fetch should be error") +} + +func TestPriceFloorFetcherWorker(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "5") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 10, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherWorkerRetry(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(nil, 500)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: nil, + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, 1, info.retryCount, "Retry Count is not 1") +} + +func TestPriceFloorFetcherWorkerDefaultCacheExpiry(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + defer close(fetcherInstance.configReceiver) + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherSubmit(t *testing.T) { + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: pond.New(1, 1), + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: make(chan struct{}), + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer fetcherInstance.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +type testPool struct{} + +func (t *testPool) TrySubmit(task func()) bool { + return false +} + +func (t *testPool) Stop() {} + +func TestPriceFloorFetcherSubmitFailed(t *testing.T) { + fetcherInstance := &PriceFloorFetcher{ + pool: &testPool{}, + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: nil, + done: nil, + cache: nil, + } + defer fetcherInstance.pool.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + assert.Equal(t, 1, len(fetcherInstance.fetchQueue), "Unable to submit the task") +} + +func getRandomNumber() int { + rand.Seed(time.Now().UnixNano()) + min := 1 + max := 10 + return rand.Intn(max-min+1) + min +} + +func TestFetcherWhenRequestGetDifferentURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + MaxRetries: 5, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetchConfig.Fetcher.URL = fmt.Sprintf("%s?id=%d", mockHttpServer.URL, getRandomNumber()) + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") +} + +func TestFetchWhenPriceFloorsDisabled(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: false, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floors", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + data, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), data, "floor data should be nil as fetcher instance does not created") + assert.Equal(t, openrtb_ext.FetchNone, status, "floor status should be none as fetcher instance does not created") +} diff --git a/floors/floors.go b/floors/floors.go index f9e13ec05fd..3fdeb4c55ae 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -4,10 +4,12 @@ import ( "errors" "math" "math/rand" + "strings" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) type Price struct { @@ -16,21 +18,21 @@ type Price struct { } const ( - defaultCurrency string = "USD" - defaultDelimiter string = "|" - catchAll string = "*" - skipRateMin int = 0 - skipRateMax int = 100 - modelWeightMax int = 100 - modelWeightMin int = 1 - enforceRateMin int = 0 - enforceRateMax int = 100 + defaultCurrency string = "USD" + defaultDelimiter string = "|" + catchAll string = "*" + skipRateMin int = 0 + skipRateMax int = 100 + modelWeightMax int = 100 + modelWeightMin int = 1 + enforceRateMin int = 0 + enforceRateMax int = 100 + floorPrecision float64 = 0.01 ) // EnrichWithPriceFloors checks for floors enabled in account and request and selects floors data from dynamic fetched if present // else selects floors data from req.ext.prebid.floors and update request with selected floors details -func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions) []error { - +func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher) []error { if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil { return []error{errors.New("Empty bidrequest")} } @@ -39,7 +41,7 @@ func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, accoun return []error{errors.New("Floors feature is disabled at account or in the request")} } - floors, err := resolveFloors(account, bidRequestWrapper, conversions) + floors, err := resolveFloors(account, bidRequestWrapper, conversions, priceFloorFetcher) updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions) updateFloorsInRequest(bidRequestWrapper, floors) @@ -135,12 +137,26 @@ func isPriceFloorsEnabledForRequest(bidRequestWrapper *openrtb_ext.RequestWrappe } // resolveFloors does selection of floors fields from request data and dynamic fetched data if dynamic fetch is enabled -func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions) (*openrtb_ext.PriceFloorRules, []error) { +func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher) (*openrtb_ext.PriceFloorRules, []error) { var errList []error var floorRules *openrtb_ext.PriceFloorRules reqFloor := extractFloorsFromRequest(bidRequestWrapper) - if reqFloor != nil { + if reqFloor != nil && reqFloor.Location != nil && len(reqFloor.Location.URL) > 0 { + account.PriceFloors.Fetcher.URL = reqFloor.Location.URL + } + account.PriceFloors.Fetcher.AccountID = account.ID + + var fetchResult *openrtb_ext.PriceFloorRules + var fetchStatus string + if priceFloorFetcher != nil && account.PriceFloors.UseDynamicData { + fetchResult, fetchStatus = priceFloorFetcher.Fetch(account.PriceFloors) + } + + if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess { + mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) + floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) + } else if reqFloor != nil { floorRules, errList = createFloorsFrom(reqFloor, account, openrtb_ext.FetchNone, openrtb_ext.RequestLocation) } else { floorRules, errList = createFloorsFrom(nil, account, openrtb_ext.FetchNone, openrtb_ext.NoDataLocation) @@ -209,3 +225,83 @@ func updateFloorsInRequest(bidRequestWrapper *openrtb_ext.RequestWrapper, priceF bidRequestWrapper.RebuildRequest() } } + +// resolveFloorMin gets floorMin value from request and dynamic fetched data +func resolveFloorMin(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) Price { + var requestFloorMinCur, providerFloorMinCur string + var requestFloorMin, providerFloorMin float64 + + if reqFloors != nil { + requestFloorMin = reqFloors.FloorMin + requestFloorMinCur = reqFloors.FloorMinCur + if len(requestFloorMinCur) == 0 && reqFloors.Data != nil { + requestFloorMinCur = reqFloors.Data.Currency + } + } + + if fetchFloors != nil { + providerFloorMin = fetchFloors.FloorMin + providerFloorMinCur = fetchFloors.FloorMinCur + if len(providerFloorMinCur) == 0 && fetchFloors.Data != nil { + providerFloorMinCur = fetchFloors.Data.Currency + } + } + + if len(requestFloorMinCur) > 0 { + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + } + + if providerFloorMin > 0 { + if strings.Compare(providerFloorMinCur, requestFloorMinCur) == 0 || len(providerFloorMinCur) == 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: requestFloorMinCur} + } + rate, err := conversions.GetRate(providerFloorMinCur, requestFloorMinCur) + if err != nil { + return Price{FloorMin: 0, FloorMinCur: requestFloorMinCur} + } + return Price{FloorMin: roundToFourDecimals(rate * providerFloorMin), FloorMinCur: requestFloorMinCur} + } + } + + if len(providerFloorMinCur) > 0 { + if providerFloorMin > 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: providerFloorMinCur} + } + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: providerFloorMinCur} + } + } + + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + +} + +// mergeFloors does merging for floors data from request and dynamic fetch +func mergeFloors(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) *openrtb_ext.PriceFloorRules { + mergedFloors := fetchFloors.DeepCopy() + if mergedFloors.Enabled == nil { + mergedFloors.Enabled = new(bool) + } + *mergedFloors.Enabled = fetchFloors.GetEnabled() && reqFloors.GetEnabled() + + if reqFloors == nil { + return mergedFloors + } + + if reqFloors.Enforcement != nil { + mergedFloors.Enforcement = reqFloors.Enforcement.DeepCopy() + } + + floorMinPrice := resolveFloorMin(reqFloors, fetchFloors, conversions) + if floorMinPrice.FloorMin > 0 { + mergedFloors.FloorMin = floorMinPrice.FloorMin + mergedFloors.FloorMinCur = floorMinPrice.FloorMinCur + } + + if reqFloors != nil && reqFloors.Location != nil && reqFloors.Location.URL != "" { + mergedFloors.Location = ptrutil.Clone(reqFloors.Location) + } + + return mergedFloors +} diff --git a/floors/floors_test.go b/floors/floors_test.go index 7f27e1a26d5..047b6738cd3 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -3,12 +3,14 @@ package floors import ( "encoding/json" "errors" + "reflect" "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -49,6 +51,14 @@ func getCurrencyRates(rates map[string]map[string]float64) currency.Conversions return currency.NewRates(rates) } +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} + func TestEnrichWithPriceFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -364,7 +374,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates)) + ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates), &mockPriceFloorFetcher{}) if tc.bidRequestWrapper != nil { assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloor, tc.expFloorVal, tc.name) assert.Equal(t, tc.bidRequestWrapper.Imp[0].BidFloorCur, tc.expFloorCur, tc.name) @@ -392,6 +402,51 @@ func getTrue() *bool { return &trueFlag } +func getFalse() *bool { + falseFlag := false + return &falseFlag +} + +type MockFetch struct { + FakeFetch func(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) +} + +func (m *MockFetch) Stop() {} + +func (m *MockFetch) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + func TestResolveFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -406,9 +461,149 @@ func TestResolveFloors(t *testing.T) { bidRequestWrapper *openrtb_ext.RequestWrapper account config.Account conversions currency.Conversions + fetcher FloorFetcher expErr []error expFloors *openrtb_ext.PriceFloorRules }{ + { + name: "Dynamic fetch disabled, floors from request selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors formed after merging", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floormincur":"EUR","enabled":true,"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"floormin":10.11,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, { name: "Dynamic fetch disabled, only enforcement object present in req.ext", bidRequestWrapper: &openrtb_ext.RequestWrapper{ @@ -426,6 +621,7 @@ func TestResolveFloors(t *testing.T) { UseDynamicData: false, }, }, + fetcher: &MockFetch{}, expFloors: &openrtb_ext.PriceFloorRules{ Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), @@ -436,18 +632,136 @@ func TestResolveFloors(t *testing.T) { PriceFloorLocation: openrtb_ext.RequestLocation, }, }, + { + name: "Dynamic fetch enabled, floors from fetched selected and new URL is updated", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"floorendpoint":{"url":"http://test.com/floor"},"enabled":true}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "http://test.com/floor", + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil, floors from request is selected", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model 1 from req", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 7, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil and request has no floors", + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{Domain: "www.website.com"}, + }, + Imp: []openrtb2.Imp{{ID: "1234", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}}}, + Ext: json.RawMessage(`{"prebid":{}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.NoDataLocation, + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates)) + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher) assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) }) } } func printFloors(floors *openrtb_ext.PriceFloorRules) string { - fbytes, _ := json.Marshal(floors) + fbytes, _ := jsonutil.Marshal(floors) return string(fbytes) } @@ -737,3 +1051,605 @@ func TestIsPriceFloorsEnabled(t *testing.T) { }) } } + +func TestResolveFloorMin(t *testing.T) { + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + tt := []struct { + name string + reqFloors openrtb_ext.PriceFloorRules + fetchFloors openrtb_ext.PriceFloorRules + conversions currency.Conversions + expPrice Price + }{ + { + name: "FloorsMin present in request Floors only", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin, FloorMinCur and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin and FloorMinCur present in request Floors and fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "INR", + }, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin present fetched floors only", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Same Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Different Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 16.6667, FloorMinCur: "USD"}, + }, + { + name: "FloorMin present in reqFloors And FloorMinCur present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 11, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 11, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin, data currency present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 1, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 70, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in fetched Floors And data currency present in reqFloors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 2, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 2, FloorMinCur: "USD"}, + }, + { + name: "Data currency and FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "USD"}, + }, + { + name: "Empty reqFloors And Empty fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 0.0, FloorMinCur: ""}, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + price := resolveFloorMin(&tc.reqFloors, &tc.fetchFloors, getCurrencyRates(rates)) + if !reflect.DeepEqual(price.FloorMin, tc.expPrice.FloorMin) { + t.Errorf("Floor Value error: \nreturn:\t%v\nwant:\t%v", price.FloorMin, tc.expPrice.FloorMin) + } + if !reflect.DeepEqual(price.FloorMinCur, tc.expPrice.FloorMinCur) { + t.Errorf("Floor Currency error: \nreturn:\t%v\nwant:\t%v", price.FloorMinCur, tc.expPrice.FloorMinCur) + } + + }) + } +} + +func TestMergeFloors(t *testing.T) { + + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + type args struct { + reqFloors *openrtb_ext.PriceFloorRules + fetchFloors *openrtb_ext.PriceFloorRules + } + tests := []struct { + name string + args args + want *openrtb_ext.PriceFloorRules + }{ + { + name: "Fetched Floors are present and request Floors are empty", + args: args{ + reqFloors: nil, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has floors disabled", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = true)", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = false)", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has Floormin", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + }, + { + name: "Fetched Floors are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + }, + { + name: "Fetched Floors has no enable atrribute are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mergeFloors(tt.args.reqFloors, tt.args.fetchFloors, getCurrencyRates(rates)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("mergeFloors() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/floors/rule.go b/floors/rule.go index f5f74cb6acf..50c12462d28 100644 --- a/floors/rule.go +++ b/floors/rule.go @@ -9,8 +9,8 @@ import ( "github.com/golang/glog" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( @@ -38,8 +38,7 @@ const ( // getFloorCurrency returns floors currency provided in floors JSON, // if currency is not provided then defaults to USD func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { - var floorCur string - + floorCur := defaultCurrency if floorExt != nil && floorExt.Data != nil { if floorExt.Data.Currency != "" { floorCur = floorExt.Data.Currency @@ -50,10 +49,6 @@ func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { } } - if len(floorCur) == 0 { - floorCur = defaultCurrency - } - return floorCur } diff --git a/floors/rule_test.go b/floors/rule_test.go index 27be6ef0dd2..1e75956243d 100644 --- a/floors/rule_test.go +++ b/floors/rule_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -760,7 +760,7 @@ func TestGetMinFloorValue(t *testing.T) { }, want: 0.0, want1: "", - wantErr: errors.New("Error in getting FloorMin value : 'unexpected end of JSON input'"), + wantErr: errors.New("Error in getting FloorMin value : 'expects \" or n, but found \x00'"), }, } for _, tc := range testCases { diff --git a/floors/validate.go b/floors/validate.go index 5624735c852..5dd843b13e0 100644 --- a/floors/validate.go +++ b/floors/validate.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) var validSchemaDimensions = map[string]struct{}{ diff --git a/floors/validate_test.go b/floors/validate_test.go index 96dad819e06..59d08afc5c0 100644 --- a/floors/validate_test.go +++ b/floors/validate_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/aggregated_config.go b/gdpr/aggregated_config.go index bbfb503225d..4bd1533de0f 100644 --- a/gdpr/aggregated_config.go +++ b/gdpr/aggregated_config.go @@ -3,8 +3,8 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // TCF2ConfigReader is an interface to access TCF2 configurations diff --git a/gdpr/aggregated_config_test.go b/gdpr/aggregated_config_test.go index bf2d3bbb8f8..54d1c901853 100644 --- a/gdpr/aggregated_config_test.go +++ b/gdpr/aggregated_config_test.go @@ -5,8 +5,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/basic_enforcement.go b/gdpr/basic_enforcement.go index f4559c4643d..322bb30986f 100644 --- a/gdpr/basic_enforcement.go +++ b/gdpr/basic_enforcement.go @@ -2,7 +2,7 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BasicEnforcement determines if legal basis is satisfied for a given purpose and bidder using diff --git a/gdpr/basic_enforcement_test.go b/gdpr/basic_enforcement_test.go index c49e59ea595..06472618a83 100644 --- a/gdpr/basic_enforcement_test.go +++ b/gdpr/basic_enforcement_test.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/consent_parser.go b/gdpr/consent_parser.go new file mode 100644 index 00000000000..3a354886036 --- /dev/null +++ b/gdpr/consent_parser.go @@ -0,0 +1,67 @@ +package gdpr + +import ( + "errors" + "fmt" + + "github.com/prebid/go-gdpr/api" + "github.com/prebid/go-gdpr/vendorconsent" + tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" +) + +// parsedConsent represents a parsed consent string containing notable version information and a convenient +// metadata object that allows easy examination of encoded purpose and vendor information +type parsedConsent struct { + encodingVersion uint8 + listVersion uint16 + specVersion uint16 + consentMeta tcf2.ConsentMetadata +} + +// parseConsent parses and validates the specified consent string returning an instance of parsedConsent +func parseConsent(consent string) (*parsedConsent, error) { + pc, err := vendorconsent.ParseString(consent) + if err != nil { + return nil, &ErrorMalformedConsent{ + Consent: consent, + Cause: err, + } + } + if err = validateVersions(pc); err != nil { + return nil, &ErrorMalformedConsent{ + Consent: consent, + Cause: err, + } + } + cm, ok := pc.(tcf2.ConsentMetadata) + if !ok { + err = errors.New("Unable to access TCF2 parsed consent") + return nil, err + } + return &parsedConsent{ + encodingVersion: pc.Version(), + listVersion: pc.VendorListVersion(), + specVersion: getSpecVersion(pc.TCFPolicyVersion()), + consentMeta: cm, + }, nil +} + +// validateVersions ensures that certain version fields in the consent string contain valid values. +// An error is returned if at least one of them is invalid +func validateVersions(pc api.VendorConsents) (err error) { + version := pc.Version() + if version != 2 { + return fmt.Errorf("invalid encoding format version: %d", version) + } + return +} + +// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification +// version that should be used to calculate legal basis. A zero value is returned if the policy version +// is invalid +func getSpecVersion(policyVersion uint8) uint16 { + if policyVersion >= 4 { + return 3 + } + return 2 +} diff --git a/gdpr/consent_parser_test.go b/gdpr/consent_parser_test.go new file mode 100644 index 00000000000..4708f689f05 --- /dev/null +++ b/gdpr/consent_parser_test.go @@ -0,0 +1,160 @@ +package gdpr + +import ( + "errors" + "testing" + "time" + + "github.com/prebid/go-gdpr/consentconstants" + + "github.com/stretchr/testify/assert" +) + +func TestParseConsent(t *testing.T) { + validTCF1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" + validTCF2Consent := "CPuKGCPPuKGCPNEAAAENCZCAAAAAAAAAAAAAAAAAAAAA" + + tests := []struct { + name string + consent string + expectedEncodingVersion uint8 + expectedListVersion uint16 + expectedSpecVersion uint16 + expectedError error + }{ + + { + name: "valid_consent_with_encoding_version_2", + consent: validTCF2Consent, + expectedEncodingVersion: 2, + expectedListVersion: 153, + expectedSpecVersion: 2, + }, + { + name: "invalid_consent_parsing_error", + consent: "", + expectedError: &ErrorMalformedConsent{ + Consent: "", + Cause: consentconstants.ErrEmptyDecodedConsent, + }, + }, + { + name: "invalid_consent_version_validation_error", + consent: validTCF1Consent, + expectedError: &ErrorMalformedConsent{ + Consent: validTCF1Consent, + Cause: errors.New("invalid encoding format version: 1"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parsedConsent, err := parseConsent(tt.consent) + + if tt.expectedError != nil { + assert.Equal(t, tt.expectedError, err) + assert.Nil(t, parsedConsent) + } else { + assert.NoError(t, err) + assert.NotNil(t, parsedConsent) + assert.Equal(t, uint8(2), parsedConsent.encodingVersion) + assert.Equal(t, uint16(153), parsedConsent.listVersion) + assert.Equal(t, uint16(2), parsedConsent.specVersion) + assert.Equal(t, tt.expectedEncodingVersion, parsedConsent.consentMeta.Version()) + assert.Equal(t, tt.expectedListVersion, parsedConsent.consentMeta.VendorListVersion()) + } + }) + } +} + +func TestValidateVersions(t *testing.T) { + tests := []struct { + name string + version uint8 + expectedError error + }{ + { + name: "valid_consent_version=2", + version: 2, + }, + { + name: "invalid_consent_version<2", + version: 1, + expectedError: errors.New("invalid encoding format version: 1"), + }, + { + name: "invalid_consent_version>2", + version: 3, + expectedError: errors.New("invalid encoding format version: 3"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mcs := mockConsentString{ + version: tt.version, + } + err := validateVersions(&mcs) + if tt.expectedError != nil { + assert.Equal(t, tt.expectedError, err) + } else { + assert.Nil(t, err) + } + }) + } +} + +func TestGetSpecVersion(t *testing.T) { + tests := []struct { + name string + policyVersion uint8 + expectedSpecVersion uint16 + }{ + { + name: "policy_version_0_gives_spec_version_2", + policyVersion: 0, + expectedSpecVersion: 2, + }, + { + name: "policy_version_3_gives_spec_version_2", + policyVersion: 3, + expectedSpecVersion: 2, + }, + { + name: "policy_version_4_gives_spec_version_3", + policyVersion: 4, + expectedSpecVersion: 3, + }, + { + name: "policy_version_5_gives_spec_version_3", + policyVersion: 5, + expectedSpecVersion: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + specVersion := getSpecVersion(tt.policyVersion) + assert.Equal(t, tt.expectedSpecVersion, specVersion) + }) + } +} + +type mockConsentString struct { + version uint8 + policyVersion uint8 +} + +func (mcs *mockConsentString) Version() uint8 { return mcs.version } +func (mcs *mockConsentString) Created() time.Time { return time.Time{} } +func (mcs *mockConsentString) LastUpdated() time.Time { return time.Time{} } +func (mcs *mockConsentString) CmpID() uint16 { return 0 } +func (mcs *mockConsentString) CmpVersion() uint16 { return 0 } +func (mcs *mockConsentString) ConsentScreen() uint8 { return 0 } +func (mcs *mockConsentString) ConsentLanguage() string { return "" } +func (mcs *mockConsentString) VendorListVersion() uint16 { return 0 } +func (mcs *mockConsentString) TCFPolicyVersion() uint8 { return mcs.policyVersion } +func (mcs *mockConsentString) MaxVendorID() uint16 { return 0 } +func (mcs *mockConsentString) PurposeAllowed(id consentconstants.Purpose) bool { return false } +func (mcs *mockConsentString) VendorConsent(id uint16) bool { return false } diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go index b86f7a3ff88..c872e13e454 100644 --- a/gdpr/full_enforcement.go +++ b/gdpr/full_enforcement.go @@ -2,7 +2,7 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( @@ -67,6 +67,9 @@ func (fe *FullEnforcement) applyEnforceOverrides(overrides Overrides) (enforcePu // must declare the purpose as either consent or flex and the user must consent in accordance with // the purpose configs. func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool { + if vi.vendor == nil { + return false + } if !vi.vendor.Purpose(fe.cfg.PurposeID) { return false } @@ -84,6 +87,9 @@ func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi V // established, the vendor must declare the purpose as either legit interest or flex and the user // must have been provided notice for the legit interest basis in accordance with the purpose configs. func (fe *FullEnforcement) legitInterestEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool { + if vi.vendor == nil { + return false + } if !vi.vendor.LegitimateInterest(fe.cfg.PurposeID) { return false } diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go index e8ccd1e4c0c..dac9d7ef76a 100644 --- a/gdpr/full_enforcement_test.go +++ b/gdpr/full_enforcement_test.go @@ -1,7 +1,6 @@ package gdpr import ( - "encoding/json" "testing" "github.com/prebid/go-gdpr/consentconstants" @@ -9,7 +8,8 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -898,10 +898,70 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { } } +func TestLegalBasisWithoutVendor(t *testing.T) { + P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA" + tests := []struct { + name string + config purposeConfig + wantResult bool + }{ + { + name: "enforce_purpose_&_vendors_off", + config: purposeConfig{ + EnforcePurpose: false, + EnforceVendors: false, + }, + wantResult: true, + }, + { + name: "enforce_purpose_on,_bidder_is_a_vendor_exception", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: false, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + }, + wantResult: true, + }, + { + name: "enforce_purpose_on", + config: purposeConfig{ + EnforcePurpose: true, + EnforceVendors: false, + }, + wantResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // convert consent string to TCF2 object + parsedConsent, err := vendorconsent.ParseString(P1P2P3PurposeConsent) + if err != nil { + t.Fatalf("Failed to parse consent %s\n", P1P2P3PurposeConsent) + } + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + t.Fatalf("Failed to convert consent %s\n", P1P2P3PurposeConsent) + } + + vendorInfo := VendorInfo{ + vendorID: 32, + vendor: nil, + } + + enforcer := FullEnforcement{cfg: tt.config} + enforcer.cfg.PurposeID = consentconstants.Purpose(3) + + result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + assert.Equal(t, tt.wantResult, result) + }) + } +} + func getVendorList(t *testing.T) vendorlist.VendorList { GVL := makeVendorList() - marshaledGVL, err := json.Marshal(GVL) + marshaledGVL, err := jsonutil.Marshal(GVL) if err != nil { t.Fatalf("Failed to marshal GVL") } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index db6aa125383..1b4f6cb4680 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -3,8 +3,8 @@ package gdpr import ( "context" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type Permissions interface { diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 1e56c5fdede..9604e24f4f0 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -6,8 +6,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/impl.go b/gdpr/impl.go index 4869a84d977..fd3ad2b2dd9 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -2,14 +2,11 @@ package gdpr import ( "context" - "errors" - "fmt" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const noBidder openrtb_ext.BidderName = "" @@ -63,53 +60,27 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCo if _, ok := p.nonStandardPublishers[p.publisherID]; ok { return AllowAll, nil } - if p.gdprSignal != SignalYes { return AllowAll, nil } - if p.consent == "" { return p.defaultPermissions(), nil } - - // note the bidder here is guaranteed to be enabled - vendorID, vendorFound := p.resolveVendorID(bidderCoreName, bidder) - basicEnforcementVendors := p.cfg.BasicEnforcementVendors() - _, weakVendorEnforcement := basicEnforcementVendors[string(bidder)] - - if !vendorFound && !weakVendorEnforcement { - return DenyAll, nil - } - - parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent) + pc, err := parseConsent(p.consent) if err != nil { return p.defaultPermissions(), err } - - // vendor will be nil if not a valid TCF2 consent string - if vendor == nil { - if weakVendorEnforcement && parsedConsent.Version() == 2 { - vendor = vendorTrue{} - } else { - return p.defaultPermissions(), nil - } - } - - if !p.cfg.IsEnabled() { - return AllowBidRequestOnly, nil - } - - consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err = fmt.Errorf("Unable to access TCF2 parsed consent") + vendorID, _ := p.resolveVendorID(bidderCoreName, bidder) + vendor, err := p.getVendor(ctx, vendorID, *pc) + if err != nil { return p.defaultPermissions(), err } - vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} + permissions = AuctionPermissions{} - permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, consentMeta, vendorInfo) - permissions.PassGeo = p.allowGeo(bidderCoreName, consentMeta, vendor) - permissions.PassID = p.allowID(bidderCoreName, consentMeta, vendorInfo) + permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo) + permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor) + permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo) return permissions, nil } @@ -148,34 +119,28 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder if p.consent == "" { return false, nil } - - parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent) + pc, err := parseConsent(p.consent) if err != nil { return false, err } - - if vendor == nil { + vendor, err := p.getVendor(ctx, vendorID, *pc) + if err != nil { return false, nil } + vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} if !p.cfg.PurposeEnforced(consentconstants.Purpose(1)) { return true, nil } - consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err := errors.New("Unable to access TCF2 parsed consent") - return false, err - } - if p.cfg.PurposeOneTreatmentEnabled() && consentMeta.PurposeOneTreatment() { + if p.cfg.PurposeOneTreatmentEnabled() && pc.consentMeta.PurposeOneTreatment() { return p.cfg.PurposeOneTreatmentAccessAllowed(), nil } purpose := consentconstants.Purpose(1) enforcer := p.purposeEnforcerBuilder(purpose, bidder) - vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} - if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, Overrides{blockVendorExceptions: !vendorException}) { + if enforcer.LegalBasis(vendorInfo, bidder, pc.consentMeta, Overrides{blockVendorExceptions: !vendorException}) { return true, nil } return false, nil @@ -205,7 +170,7 @@ func (p *permissionsImpl) allowGeo(bidder openrtb_ext.BidderName, consentMeta tc basicEnforcementVendors := p.cfg.BasicEnforcementVendors() _, weakVendorEnforcement := basicEnforcementVendors[string(bidder)] - return consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement) + return consentMeta.SpecialFeatureOptIn(1) && ((vendor != nil && vendor.SpecialFeature(1)) || weakVendorEnforcement) } // allowID computes the pass user ID activity legal basis for a given bidder using the enforcement algorithms @@ -229,53 +194,13 @@ func (p *permissionsImpl) allowID(bidder openrtb_ext.BidderName, consentMeta tcf return false } -// parseVendor parses the consent string and fetches the specified vendor's information from the GVL -func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) { - parsedConsent, err = vendorconsent.ParseString(consent) +// getVendor retrieves the GVL vendor information for a particular bidder +func (p *permissionsImpl) getVendor(ctx context.Context, vendorID uint16, pc parsedConsent) (api.Vendor, error) { + vendorList, err := p.fetchVendorList(ctx, pc.specVersion, pc.listVersion) if err != nil { - err = &ErrorMalformedConsent{ - Consent: consent, - Cause: err, - } - return - } - - version := parsedConsent.Version() - if version != 2 { - return - } - - policyVersion := parsedConsent.TCFPolicyVersion() - specVersion, err := getSpecVersion(policyVersion) - if err != nil { - err = &ErrorMalformedConsent{ - Consent: consent, - Cause: err, - } - return - } - - vendorList, err := p.fetchVendorList(ctx, uint16(specVersion), parsedConsent.VendorListVersion()) - if err != nil { - return - } - - vendor = vendorList.Vendor(vendorID) - return -} - -// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification -// version that should be used to calculate legal basis -func getSpecVersion(policyVersion uint8) (uint16, error) { - var specVersion uint16 = 3 - - if policyVersion > 4 { - return 0, fmt.Errorf("invalid TCF policy version %d", policyVersion) + return nil, err } - if policyVersion < 4 { - specVersion = 2 - } - return specVersion, nil + return vendorList.Vendor(vendorID), nil } // AllowHostCookies represents a GDPR permissions policy with host cookie syncing always allowed @@ -300,25 +225,3 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) { return AllowAll, nil } - -// vendorTrue claims everything. -type vendorTrue struct{} - -func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool { - return true -} -func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool { - return true -} -func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool { - return true -} -func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool { - return true -} -func (v vendorTrue) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) { - return true -} -func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) { - return true -} diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 4a65dbafec7..fc3d69d9c57 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -9,8 +9,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -342,6 +342,109 @@ func TestAllowActivities(t *testing.T) { } } +func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) { + bidderWithoutGVLID := openrtb_ext.BidderPangle + purpose2Consent := "CPuDXznPuDXznMOAAAENCZCAAEAAAAAAAAAAAAAAAAAA" + noPurposeConsent := "CPuDXznPuDXznMOAAAENCZCAAAAAAAAAAAAAAAAAAAAA" + + tests := []struct { + name string + enforceAlgoID config.TCF2EnforcementAlgo + vendorExceptions map[openrtb_ext.BidderName]struct{} + basicEnforcementVendors map[string]struct{} + consent string + allowBidRequest bool + passID bool + }{ + { + name: "full_enforcement_no_exceptions_user_consents_to_purpose_2", + enforceAlgoID: config.TCF2FullEnforcement, + consent: purpose2Consent, + }, + { + name: "full_enforcement_vendor_exception_user_consents_to_purpose_2", + enforceAlgoID: config.TCF2FullEnforcement, + vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}}, + consent: purpose2Consent, + allowBidRequest: true, + passID: true, + }, + { + name: "basic_enforcement_no_exceptions_user_consents_to_purpose_2", + consent: purpose2Consent, + }, + { + name: "basic_enforcement_vendor_exception_user_consents_to_purpose_2", + vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}}, + consent: purpose2Consent, + allowBidRequest: true, + passID: true, + }, + { + name: "full_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID + enforceAlgoID: config.TCF2FullEnforcement, + basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}}, + consent: purpose2Consent, + allowBidRequest: true, + passID: true, + }, + { + name: "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID + enforceAlgoID: config.TCF2BasicEnforcement, + basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}}, + consent: purpose2Consent, + allowBidRequest: true, + passID: true, + }, + { + name: "full_enforcement_soft_vendor_exception_user_consents_to_purpose_4", + enforceAlgoID: config.TCF2FullEnforcement, + basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}}, + consent: noPurposeConsent, + allowBidRequest: false, + passID: false, + }, + { + name: "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_4", + enforceAlgoID: config.TCF2BasicEnforcement, + basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}}, + consent: noPurposeConsent, + allowBidRequest: false, + passID: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tcf2AggConfig := allPurposesEnabledTCF2Config() + tcf2AggConfig.AccountConfig.BasicEnforcementVendorsMap = tt.basicEnforcementVendors + tcf2AggConfig.HostConfig.Purpose2.VendorExceptionMap = tt.vendorExceptions + tcf2AggConfig.HostConfig.Purpose2.EnforceAlgoID = tt.enforceAlgoID + tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2 + + perms := permissionsImpl{ + cfg: &tcf2AggConfig, + consent: tt.consent, + gdprSignal: SignalYes, + hostVendorID: 2, + nonStandardPublishers: map[string]struct{}{}, + vendorIDs: map[openrtb_ext.BidderName]uint16{}, + fetchVendorList: listFetcher(map[uint16]map[uint16]vendorlist.VendorList{ + 2: { + 153: parseVendorListDataV2(t, MarshalVendorList(vendorList{GVLSpecificationVersion: 2, VendorListVersion: 153, Vendors: map[string]*vendor{}})), + }, + }), + purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), + } + + permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID) + assert.NoError(t, err) + assert.Equal(t, tt.allowBidRequest, permissions.AllowBidRequest) + assert.Equal(t, tt.passID, permissions.PassID) + }) + } +} + func buildVendorList34() vendorList { return vendorList{ VendorListVersion: 2, @@ -1006,28 +1109,6 @@ func TestAllowActivitiesBidRequests(t *testing.T) { } } -func TestTCF1Consent(t *testing.T) { - bidderAllowedByConsent := openrtb_ext.BidderAppnexus - tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" - - perms := permissionsImpl{ - cfg: &tcf2Config{}, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - }, - aliasGVLIDs: map[string]uint16{}, - consent: tcf1Consent, - gdprSignal: SignalYes, - } - - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent) - - assert.Nil(t, err, "TCF1 consent - no error returned") - assert.Equal(t, true, permissions.AllowBidRequest, "TCF1 consent - bid request not allowed") - assert.Equal(t, true, permissions.PassGeo, "TCF1 consent - passing geo not allowed") - assert.Equal(t, false, permissions.PassID, "TCF1 consent - passing id not allowed") -} - func TestAllowActivitiesVendorException(t *testing.T) { noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA" noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" @@ -1244,53 +1325,9 @@ func TestDefaultPermissions(t *testing.T) { } } -func TestGetSpecVersion(t *testing.T) { - tests := []struct { - name string - policyVersion uint8 - expectedSpecVersion uint16 - expectedError bool - }{ - { - name: "policy_version_0_gives_spec_version_2", - policyVersion: 0, - expectedSpecVersion: 2, - }, - { - name: "policy_version_3_gives_spec_version_2", - policyVersion: 3, - expectedSpecVersion: 2, - }, - { - name: "policy_version_4_spec_version_3", - policyVersion: 4, - expectedSpecVersion: 3, - }, - { - name: "policy_version_5_error", - policyVersion: 5, - expectedSpecVersion: 0, - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - specVersion, err := getSpecVersion(tt.policyVersion) - assert.Equal(t, tt.expectedSpecVersion, specVersion) - if tt.expectedError { - assert.Error(t, err) - } else { - assert.Nil(t, err) - } - }) - } -} - func TestVendorListSelection(t *testing.T) { policyVersion3WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABDAAIAAAAAAAAAAACEAAAAA" policyVersion4WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABEAAIAAAAAAAAAAACEAAAAA" - policyVersion5WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABFAAIAAAAAAAAAAACEAAAAA" specVersion2vendorListData := MarshalVendorList(vendorList{ GVLSpecificationVersion: 2, @@ -1359,11 +1396,6 @@ func TestVendorListSelection(t *testing.T) { consent: policyVersion4WithVendor2AndPurpose1Consent, expectedAllowSync: true, }, - { - name: "consent_tcf_policy_version_5_causes_error", - consent: policyVersion5WithVendor2AndPurpose1Consent, - expectedErr: true, - }, } for _, tt := range tests { diff --git a/gdpr/purpose_config.go b/gdpr/purpose_config.go index 015f23269ef..09edef94384 100644 --- a/gdpr/purpose_config.go +++ b/gdpr/purpose_config.go @@ -2,8 +2,8 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // purposeConfig represents all of the config info selected from the host and account configs for diff --git a/gdpr/purpose_config_test.go b/gdpr/purpose_config_test.go index e80733cc8ca..4837b62a2aa 100644 --- a/gdpr/purpose_config_test.go +++ b/gdpr/purpose_config_test.go @@ -3,7 +3,7 @@ package gdpr import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/purpose_enforcer.go b/gdpr/purpose_enforcer.go index c8e76f988aa..4a138e76cf8 100644 --- a/gdpr/purpose_enforcer.go +++ b/gdpr/purpose_enforcer.go @@ -4,8 +4,8 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // PurposeEnforcer represents the enforcement strategy for determining if legal basis is achieved for a purpose diff --git a/gdpr/purpose_enforcer_test.go b/gdpr/purpose_enforcer_test.go index ea2075d9c65..ed1176b12f3 100644 --- a/gdpr/purpose_enforcer_test.go +++ b/gdpr/purpose_enforcer_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/signal.go b/gdpr/signal.go index ed7fe1dd8ea..3d2e4de1251 100644 --- a/gdpr/signal.go +++ b/gdpr/signal.go @@ -3,7 +3,7 @@ package gdpr import ( "strconv" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) type Signal int diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index da6bdbae415..64424f5ee69 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -14,7 +14,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "golang.org/x/net/context/ctxhttp" ) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index a1dfb7fefb8..98dc4ba5aa3 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -2,7 +2,6 @@ package gdpr import ( "context" - "encoding/json" "net/http" "net/http/httptest" "strconv" @@ -12,7 +11,8 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestFetcherDynamicLoadListExists(t *testing.T) { @@ -305,7 +305,7 @@ type vendor struct { } func MarshalVendorList(vendorList vendorList) string { - json, _ := json.Marshal(vendorList) + json, _ := jsonutil.Marshal(vendorList) return string(json) } diff --git a/go.mod b/go.mod index 3833ef3d9e5..9a55cddb00a 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ -module github.com/prebid/prebid-server +module github.com/prebid/prebid-server/v2 -go 1.19 +go 1.20 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 + github.com/alitto/pond v1.8.3 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/benbjohnson/clock v1.3.0 github.com/buger/jsonparser v1.1.1 @@ -14,7 +15,8 @@ require ( github.com/docker/go-units v0.4.0 github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/golang/glog v1.0.0 + github.com/golang/glog v1.1.0 + github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 @@ -31,25 +33,27 @@ require ( github.com/vrischmann/go-metrics-influxdb v0.1.1 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yudai/gojsondiff v1.0.0 - golang.org/x/net v0.7.0 - golang.org/x/text v0.7.0 - google.golang.org/grpc v1.46.2 + golang.org/x/net v0.17.0 + golang.org/x/text v0.14.0 + google.golang.org/grpc v1.56.3 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -66,10 +70,10 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/sys v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index e1226ab1f3c..ad2d5ba94b8 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= +github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -86,8 +88,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -125,7 +128,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -160,8 +162,8 @@ github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -191,8 +193,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -210,7 +213,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -296,6 +299,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -356,9 +360,11 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -392,8 +398,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prebid/go-gdpr v1.11.0 h1:QbMjscuw3Ul0mDVWeMy5tP0Kii6lmTSSVhV6fm8rY9s= -github.com/prebid/go-gdpr v1.11.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= github.com/prebid/go-gdpr v1.12.0 h1:OrjQ7Uc+lCRYaOirQ48jjG/PBMvZsKNAaRTgzxN6iZ0= github.com/prebid/go-gdpr v1.12.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0= @@ -526,8 +530,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -612,8 +616,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -724,8 +728,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -738,8 +742,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -910,8 +914,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -942,9 +946,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -959,8 +962,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hooks/empty_plan.go b/hooks/empty_plan.go index 01e01843324..514d3824898 100644 --- a/hooks/empty_plan.go +++ b/hooks/empty_plan.go @@ -1,8 +1,8 @@ package hooks import ( - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) // EmptyPlanBuilder implements the ExecutionPlanBuilder interface diff --git a/hooks/hookanalytics/analytics_test.go b/hooks/hookanalytics/analytics_test.go index 177a9335da9..27584cf0d39 100644 --- a/hooks/hookanalytics/analytics_test.go +++ b/hooks/hookanalytics/analytics_test.go @@ -1,9 +1,9 @@ package hookanalytics import ( - "encoding/json" "testing" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -50,7 +50,7 @@ func TestAnalytics(t *testing.T) { Activity{Name: "define-blocks", Status: ActivityStatusError}, ) - gotAnalytics, err := json.Marshal(analytics) + gotAnalytics, err := jsonutil.Marshal(analytics) assert.NoError(t, err, "Failed to marshal analytics: %s", err) assert.JSONEq(t, string(expectedAnalytics), string(gotAnalytics)) } diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index 5f7cc3ab188..f7b6a9d32e1 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -4,17 +4,19 @@ import ( "sync" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/privacy" ) // executionContext holds information passed to module's hook during hook execution. type executionContext struct { - endpoint string - stage string - accountId string - account *config.Account - moduleContexts *moduleContexts + endpoint string + stage string + accountID string + account *config.Account + moduleContexts *moduleContexts + activityControl privacy.ActivityControl } func (ctx executionContext) getModuleContext(moduleName string) hookstage.ModuleInvocationContext { diff --git a/hooks/hookexecution/enricher.go b/hooks/hookexecution/enricher.go index 2978c21957d..ff7f8dd562e 100644 --- a/hooks/hookexecution/enricher.go +++ b/hooks/hookexecution/enricher.go @@ -5,7 +5,8 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -52,7 +53,7 @@ func EnrichExtBidResponse( return ext, warnings, err } - response, err := json.Marshal(extPrebid{Prebid: extModules{Modules: modules}}) + response, err := jsonutil.Marshal(extPrebid{Prebid: extModules{Modules: modules}}) if err != nil { return ext, warnings, err } @@ -83,7 +84,7 @@ func GetModulesJSON( return nil, warnings, nil } - data, err := json.Marshal(modulesOutcome) + data, err := jsonutil.Marshal(modulesOutcome) return data, warnings, err } diff --git a/hooks/hookexecution/enricher_test.go b/hooks/hookexecution/enricher_test.go index 7bb19c2ecf2..e0732dfe13b 100644 --- a/hooks/hookexecution/enricher_test.go +++ b/hooks/hookexecution/enricher_test.go @@ -7,9 +7,10 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -258,7 +259,7 @@ func TestGetModulesJSON(t *testing.T) { assert.Empty(t, expectedResponse) } else { var expectedExtBidResponse openrtb_ext.ExtBidResponse - err := json.Unmarshal(expectedResponse, &expectedExtBidResponse) + err := jsonutil.UnmarshalValid(expectedResponse, &expectedExtBidResponse) assert.NoError(t, err, "Failed to unmarshal prebid response extension") assert.JSONEq(t, string(expectedExtBidResponse.Prebid.Modules), string(modules)) } @@ -271,7 +272,7 @@ func getStageOutcomes(t *testing.T, file string) []StageOutcome { var stageOutcomesTest []StageOutcomeTest data := readFile(t, file) - err := json.Unmarshal(data, &stageOutcomesTest) + err := jsonutil.UnmarshalValid(data, &stageOutcomesTest) require.NoError(t, err, "Failed to unmarshal stage outcomes: %s", err) for _, stageT := range stageOutcomesTest { diff --git a/hooks/hookexecution/errors.go b/hooks/hookexecution/errors.go index b1cf912ccee..1d016e26019 100644 --- a/hooks/hookexecution/errors.go +++ b/hooks/hookexecution/errors.go @@ -3,7 +3,7 @@ package hookexecution import ( "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) // TimeoutError indicates exceeding of the max execution time allotted for hook. diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go index 18c927896b9..05cc5fb5943 100644 --- a/hooks/hookexecution/execution.go +++ b/hooks/hookexecution/execution.go @@ -3,13 +3,15 @@ package hookexecution import ( "context" "fmt" + "github.com/prebid/prebid-server/v2/ortb" "strings" "sync" "time" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/privacy" ) type hookResponse[T any] struct { @@ -66,10 +68,11 @@ func executeGroup[H any, P any]( for _, hook := range group.Hooks { mCtx := executionCtx.getModuleContext(hook.Module) + newPayload := handleModuleActivities(hook.Code, executionCtx.activityControl, payload) wg.Add(1) go func(hw hooks.HookWrapper[H], moduleCtx hookstage.ModuleInvocationContext) { defer wg.Done() - executeHook(moduleCtx, hw, payload, hookHandler, group.Timeout, resp, rejected) + executeHook(moduleCtx, hw, newPayload, hookHandler, group.Timeout, resp, rejected) }(hook, mCtx) } @@ -176,7 +179,7 @@ func handleHookResponse[P any]( metricEngine metrics.MetricsEngine, ) (P, HookOutcome, *RejectError) { var rejectErr *RejectError - labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountId} + labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountID} metricEngine.RecordModuleCalled(labels, hr.ExecutionTime) hookOutcome := HookOutcome{ @@ -311,3 +314,29 @@ func handleHookMutations[P any]( return payload } + +func handleModuleActivities[P any](hookCode string, activityControl privacy.ActivityControl, payload P) P { + payloadData, ok := any(&payload).(hookstage.RequestUpdater) + if !ok { + return payload + } + + scopeGeneral := privacy.Component{Type: privacy.ComponentTypeGeneral, Name: hookCode} + transmitUserFPDActivityAllowed := activityControl.Allow(privacy.ActivityTransmitUserFPD, scopeGeneral, privacy.ActivityRequest{}) + + if !transmitUserFPDActivityAllowed { + // changes need to be applied to new payload and leave original payload unchanged + bidderReq := payloadData.GetBidderRequestPayload() + + bidderReqCopy := ortb.CloneBidderReq(bidderReq.BidRequest) + + privacy.ScrubUserFPD(bidderReqCopy) + + var newPayload = payload + var np = any(&newPayload).(hookstage.RequestUpdater) + np.SetBidderRequestPayload(bidderReqCopy) + return newPayload + } + + return payload +} diff --git a/hooks/hookexecution/execution_test.go b/hooks/hookexecution/execution_test.go new file mode 100644 index 00000000000..a12175e30a0 --- /dev/null +++ b/hooks/hookexecution/execution_test.go @@ -0,0 +1,151 @@ +package hookexecution + +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestHandleModuleActivitiesBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.BidderRequestPayload + expectedPayloadData hookstage.BidderRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }, + }}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesProcessedAuctionRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.ProcessedAuctionRequestPayload + expectedPayloadData hookstage.ProcessedAuctionRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }}, + }, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesNoBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.RawAuctionRequestPayload + expectedPayloadData hookstage.RawAuctionRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData) + assert.Equal(t, test.expectedPayloadData, newPayload) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index ce516630778..518a0c628a6 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -6,13 +6,14 @@ import ( "sync" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" ) const ( @@ -34,7 +35,7 @@ type StageExecutor interface { ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) ExecuteProcessedAuctionStage(req *openrtb_ext.RequestWrapper) error - ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError + ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) ExecuteAuctionResponseStage(response *openrtb2.BidResponse) @@ -43,17 +44,19 @@ type StageExecutor interface { type HookStageExecutor interface { StageExecutor SetAccount(account *config.Account) + SetActivityControl(activityControl privacy.ActivityControl) GetOutcomes() []StageOutcome } type hookExecutor struct { - account *config.Account - accountID string - endpoint string - planBuilder hooks.ExecutionPlanBuilder - stageOutcomes []StageOutcome - moduleContexts *moduleContexts - metricEngine metrics.MetricsEngine + account *config.Account + accountID string + endpoint string + planBuilder hooks.ExecutionPlanBuilder + stageOutcomes []StageOutcome + moduleContexts *moduleContexts + metricEngine metrics.MetricsEngine + activityControl privacy.ActivityControl // Mutex needed for BidderRequest and RawBidderResponse Stages as they are run in several goroutines sync.Mutex } @@ -77,6 +80,10 @@ func (e *hookExecutor) SetAccount(account *config.Account) { e.accountID = account.ID } +func (e *hookExecutor) SetActivityControl(activityControl privacy.ActivityControl) { + e.activityControl = activityControl +} + func (e *hookExecutor) GetOutcomes() []StageOutcome { return e.stageOutcomes } @@ -160,7 +167,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request stageName := hooks.StageProcessedAuctionRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.ProcessedAuctionRequestPayload{BidRequest: request.BidRequest} + payload := hookstage.ProcessedAuctionRequestPayload{Request: request} outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entityAuctionRequest @@ -177,7 +184,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request return reject } -func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError { +func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError { plan := e.planBuilder.PlanForBidderRequestStage(e.endpoint, e.account) if len(plan) == 0 { return nil @@ -194,7 +201,7 @@ func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidde stageName := hooks.StageBidderRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.BidderRequestPayload{BidRequest: req, Bidder: bidder} + payload := hookstage.BidderRequestPayload{Request: req, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entity(bidder) outcome.Stage = stageName @@ -290,11 +297,12 @@ func (e *hookExecutor) ExecuteAuctionResponseStage(response *openrtb2.BidRespons func (e *hookExecutor) newContext(stage string) executionContext { return executionContext{ - account: e.account, - accountId: e.accountID, - endpoint: e.endpoint, - moduleContexts: e.moduleContexts, - stage: stage, + account: e.account, + accountID: e.accountID, + endpoint: e.endpoint, + moduleContexts: e.moduleContexts, + stage: stage, + activityControl: e.activityControl, } } @@ -316,6 +324,8 @@ type EmptyHookExecutor struct{} func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {} +func (executor EmptyHookExecutor) SetActivityControl(_ privacy.ActivityControl) {} + func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome { return []StageOutcome{} } @@ -332,7 +342,7 @@ func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.Re return nil } -func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError { +func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb_ext.RequestWrapper, bidder string) *RejectError { return nil } diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 68b990bb595..1fb299204ec 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -9,15 +9,17 @@ import ( "time" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/metrics" + metricsConfig "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/privacy" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -37,7 +39,7 @@ func TestEmptyHookExecutor(t *testing.T) { entrypointBody, entrypointRejectErr := executor.ExecuteEntrypointStage(req, body) rawAuctionBody, rawAuctionRejectErr := executor.ExecuteRawAuctionStage(body) processedAuctionRejectErr := executor.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) - bidderRequestRejectErr := executor.ExecuteBidderRequestStage(bidderRequest, "bidder-name") + bidderRequestRejectErr := executor.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: bidderRequest}, "bidder-name") executor.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) outcomes := executor.GetOutcomes() @@ -674,8 +676,11 @@ func TestExecuteRawAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) + exec.SetAccount(test.givenAccount) newBody, reject := exec.ExecuteRawAuctionStage([]byte(test.givenBody)) assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") @@ -896,6 +901,10 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) err := exec.ExecuteProcessedAuctionStage(&test.givenRequest) @@ -938,6 +947,7 @@ func TestExecuteBidderRequestStage(t *testing.T) { expectedReject *RejectError expectedModuleContexts *moduleContexts expectedStageOutcomes []StageOutcome + privacyConfig *config.AccountPrivacy }{ { description: "Payload not changed if hook execution plan empty", @@ -1169,9 +1179,12 @@ func TestExecuteBidderRequestStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) - reject := exec.ExecuteBidderRequestStage(test.givenBidderRequest, bidderName) + reject := exec.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: test.givenBidderRequest}, bidderName) assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") assert.Equal(t, test.expectedBidderRequest, test.givenBidderRequest, "Incorrect bidder request.") @@ -1187,6 +1200,29 @@ func TestExecuteBidderRequestStage(t *testing.T) { } } +func getTransmitUFPDActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func buildDefaultActivityConfig(componentName string, allow bool) config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"general"}, + }, + }, + }, + } +} + func TestExecuteRawBidderResponseStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} account := &config.Account{} @@ -1390,6 +1426,10 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) reject := exec.ExecuteRawBidderResponseStage(&test.givenBidderResponse, "the-bidder") @@ -1669,6 +1709,10 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) exec.ExecuteAllProcessedBidResponsesStage(test.givenBiddersResponse) @@ -1918,6 +1962,10 @@ func TestExecuteAuctionResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) + + privacyConfig := getTransmitUFPDActivityConfig("foo", false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.SetAccount(test.givenAccount) exec.ExecuteAuctionResponseStage(test.givenResponse) diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index e8670ff89e8..c47e2892cc0 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -5,8 +5,8 @@ import ( "errors" "time" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) type mockUpdateHeaderEntrypointHook struct{} @@ -131,7 +131,7 @@ func (e mockTimeoutHook) HandleProcessedAuctionHook(_ context.Context, _ hooksta time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation(func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.BidRequest.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -142,7 +142,7 @@ func (e mockTimeoutHook) HandleBidderRequestHook(_ context.Context, _ hookstage. time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation(func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -305,12 +305,20 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.BidRequest.User.Yob = 2000 + payload.Request.User.Yob = 2000 + userExt, err := payload.Request.GetUserExt() + if err != nil { + return payload, err + } + newPrebidExt := &openrtb_ext.ExtUserPrebid{ + BuyerUIDs: map[string]string{"some": "id"}, + } + userExt.SetPrebid(newPrebidExt) return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.BidRequest.User.Consent = "true" + payload.Request.User.Consent = "true" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) @@ -322,12 +330,12 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, _ h c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Yob = 2000 + payload.Request.User.Yob = 2000 return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Consent = "true" + payload.Request.User.Consent = "true" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) diff --git a/hooks/hookexecution/outcome.go b/hooks/hookexecution/outcome.go index 3eeb7bcef5e..ff8bf1e973e 100644 --- a/hooks/hookexecution/outcome.go +++ b/hooks/hookexecution/outcome.go @@ -3,7 +3,7 @@ package hookexecution import ( "time" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" ) // Status indicates the result of hook execution. diff --git a/hooks/hookexecution/test_utils.go b/hooks/hookexecution/test_utils.go index bd94d5778b4..32ae9fd7a22 100644 --- a/hooks/hookexecution/test_utils.go +++ b/hooks/hookexecution/test_utils.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -16,8 +17,8 @@ func AssertEqualModulesData(t *testing.T, expectedData, actualData json.RawMessa var expectedModulesOutcome ModulesOutcome var actualModulesOutcome ModulesOutcome - assert.NoError(t, json.Unmarshal(expectedData, &expectedModulesOutcome), "Failed to unmarshal expected modules data.") - assert.NoError(t, json.Unmarshal(actualData, &actualModulesOutcome), "Failed to unmarshal actual modules data.") + assert.NoError(t, jsonutil.UnmarshalValid(expectedData, &expectedModulesOutcome), "Failed to unmarshal expected modules data.") + assert.NoError(t, jsonutil.UnmarshalValid(actualData, &actualModulesOutcome), "Failed to unmarshal actual modules data.") assert.Equal(t, expectedModulesOutcome.Errors, actualModulesOutcome.Errors, "Invalid error messages.") assert.Equal(t, expectedModulesOutcome.Warnings, actualModulesOutcome.Warnings, "Invalid warning messages.") diff --git a/hooks/hookstage/allprocessedbidresponses.go b/hooks/hookstage/allprocessedbidresponses.go index 3f90c5624ee..233a68b6efd 100644 --- a/hooks/hookstage/allprocessedbidresponses.go +++ b/hooks/hookstage/allprocessedbidresponses.go @@ -3,8 +3,8 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // AllProcessedBidResponses hooks are invoked over a list of all diff --git a/hooks/hookstage/bidderrequest.go b/hooks/hookstage/bidderrequest.go index 6609a103279..05f3574c8bf 100644 --- a/hooks/hookstage/bidderrequest.go +++ b/hooks/hookstage/bidderrequest.go @@ -2,8 +2,7 @@ package hookstage import ( "context" - - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BidderRequest hooks are invoked for each bidder participating in auction. @@ -25,6 +24,20 @@ type BidderRequest interface { // distilled for the particular bidder. // Hooks are allowed to modify openrtb2.BidRequest using mutations. type BidderRequestPayload struct { - BidRequest *openrtb2.BidRequest - Bidder string + Request *openrtb_ext.RequestWrapper + Bidder string +} + +func (brp *BidderRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return brp.Request +} + +func (brp *BidderRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + brp.Request = br +} + +// RequestUpdater allows reading and writing a bid request +type RequestUpdater interface { + GetBidderRequestPayload() *openrtb_ext.RequestWrapper + SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) } diff --git a/hooks/hookstage/bidderrequest_mutations.go b/hooks/hookstage/bidderrequest_mutations.go index 6dfd1c6438f..746878cb4a1 100644 --- a/hooks/hookstage/bidderrequest_mutations.go +++ b/hooks/hookstage/bidderrequest_mutations.go @@ -2,9 +2,9 @@ package hookstage import ( "errors" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" ) func (c *ChangeSet[T]) BidderRequest() ChangeSetBidderRequest[T] { @@ -31,12 +31,12 @@ func (c ChangeSetBidderRequest[T]) BApp() ChangeSetBApp[T] { return ChangeSetBApp[T]{changeSetBidderRequest: c} } -func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb2.BidRequest, error) { +func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb_ext.RequestWrapper, error) { if payload, ok := any(p).(BidderRequestPayload); ok { - if payload.BidRequest == nil { - return nil, errors.New("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return nil, errors.New("payload contains a nil bid request") } - return payload.BidRequest, nil + return payload.Request, nil } return nil, errors.New("failed to cast BidderRequestPayload") } diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index 7f465382b20..73b210957e2 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -3,7 +3,7 @@ package hookstage import ( "encoding/json" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" ) // HookResult represents the result of execution the concrete hook instance. diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go index 30cb0dab38d..02638dccc20 100644 --- a/hooks/hookstage/processedauctionrequest.go +++ b/hooks/hookstage/processedauctionrequest.go @@ -2,8 +2,7 @@ package hookstage import ( "context" - - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ProcessedAuctionRequest hooks are invoked after the request is parsed @@ -23,8 +22,16 @@ type ProcessedAuctionRequest interface { ) (HookResult[ProcessedAuctionRequestPayload], error) } -// ProcessedAuctionRequestPayload consists of the openrtb2.BidRequest object. -// Hooks are allowed to modify openrtb2.BidRequest using mutations. +// ProcessedAuctionRequestPayload consists of the openrtb_ext.RequestWrapper object. +// Hooks are allowed to modify openrtb_ext.RequestWrapper using mutations. type ProcessedAuctionRequestPayload struct { - BidRequest *openrtb2.BidRequest + Request *openrtb_ext.RequestWrapper +} + +func (parp *ProcessedAuctionRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return parp.Request +} + +func (parp *ProcessedAuctionRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + parp.Request = br } diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index d450d6d0681..7d08a7d2e02 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v2/adapters" ) // RawBidderResponse hooks are invoked for each bidder participating in auction. diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index 61c0de10bde..efab874fa15 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -3,7 +3,7 @@ package hookstage import ( "errors" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v2/adapters" ) func (c *ChangeSet[T]) RawBidderResponse() ChangeSetRawBidderResponse[T] { diff --git a/hooks/plan.go b/hooks/plan.go index c6fda959762..a3a0e9af661 100644 --- a/hooks/plan.go +++ b/hooks/plan.go @@ -4,8 +4,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) type Stage string diff --git a/hooks/plan_test.go b/hooks/plan_test.go index 5d2a504f0d1..064403cb8cf 100644 --- a/hooks/plan_test.go +++ b/hooks/plan_test.go @@ -2,12 +2,12 @@ package hooks import ( "context" - "encoding/json" "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -227,7 +227,7 @@ func TestPlanForRawAuctionStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -333,7 +333,7 @@ func TestPlanForProcessedAuctionStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -439,7 +439,7 @@ func TestPlanForBidderRequestStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -545,7 +545,7 @@ func TestPlanForRawBidderResponseStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -651,7 +651,7 @@ func TestPlanForAllProcessedBidResponsesStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -757,7 +757,7 @@ func TestPlanForAuctionResponseStage(t *testing.T) { for name, test := range testCases { t.Run(name, func(t *testing.T) { account := new(config.Account) - if err := json.Unmarshal(test.giveAccountPlanData, &account.Hooks); err != nil { + if err := jsonutil.UnmarshalValid(test.giveAccountPlanData, &account.Hooks); err != nil { t.Fatal(err) } @@ -779,12 +779,12 @@ func getPlanBuilder( var hostPlan config.HookExecutionPlan var defaultAccountPlan config.HookExecutionPlan - err = json.Unmarshal(hostPlanData, &hostPlan) + err = jsonutil.UnmarshalValid(hostPlanData, &hostPlan) if err != nil { return nil, err } - err = json.Unmarshal(accountPlanData, &defaultAccountPlan) + err = jsonutil.UnmarshalValid(accountPlanData, &defaultAccountPlan) if err != nil { return nil, err } diff --git a/hooks/repo.go b/hooks/repo.go index 40276701b34..3d8db581bda 100644 --- a/hooks/repo.go +++ b/hooks/repo.go @@ -3,7 +3,7 @@ package hooks import ( "fmt" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) // HookRepository is the interface that exposes methods diff --git a/hooks/repo_test.go b/hooks/repo_test.go index ae523c98773..1ffbf0bfbed 100644 --- a/hooks/repo_test.go +++ b/hooks/repo_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookstage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/macros/macros.go b/macros/macros.go index 7292e0c8bbc..609e72cdec7 100644 --- a/macros/macros.go +++ b/macros/macros.go @@ -5,7 +5,7 @@ import ( "text/template" ) -// EndpointTemplateParams specifies params for an endpoint template +// EndpointTemplateParams specifies macros for bidder endpoints. type EndpointTemplateParams struct { Host string PublisherID string @@ -17,8 +17,8 @@ type EndpointTemplateParams struct { GvlID string } -// UserSyncTemplateParams specifies params for an user sync URL template -type UserSyncTemplateParams struct { +// UserSyncPrivacy specifies privacy policy macros, represented as strings, for user sync urls. +type UserSyncPrivacy struct { GDPR string GDPRConsent string USPrivacy string @@ -30,10 +30,10 @@ type UserSyncTemplateParams struct { func ResolveMacros(aTemplate *template.Template, params interface{}) (string, error) { strBuf := bytes.Buffer{} - err := aTemplate.Execute(&strBuf, params) - if err != nil { + if err := aTemplate.Execute(&strBuf, params); err != nil { return "", err } + res := strBuf.String() return res, nil } diff --git a/macros/macros_test.go b/macros/macros_test.go index 055624b611f..db1b8be9a97 100644 --- a/macros/macros_test.go +++ b/macros/macros_test.go @@ -26,7 +26,7 @@ func TestResolveMacros(t *testing.T) { }, { givenTemplate: endpointTemplate, - givenParams: UserSyncTemplateParams{GDPR: "SomeGDPR", GDPRConsent: "SomeGDPRConsent"}, + givenParams: UserSyncPrivacy{GDPR: "SomeGDPR", GDPRConsent: "SomeGDPRConsent"}, expectedResult: "", expectedError: true, }, diff --git a/macros/provider.go b/macros/provider.go index 0b0fc0de454..3cae540e22a 100644 --- a/macros/provider.go +++ b/macros/provider.go @@ -5,8 +5,8 @@ import ( "strconv" "time" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const ( diff --git a/macros/provider_test.go b/macros/provider_test.go index b6465a7f2e6..ee9663e3269 100644 --- a/macros/provider_test.go +++ b/macros/provider_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go index 97379a6d965..c9a05a83df4 100644 --- a/macros/string_index_based_replacer_test.go +++ b/macros/string_index_based_replacer_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/exchange/entities" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/main.go b/main.go index a83266665f0..e72f02f1f0e 100644 --- a/main.go +++ b/main.go @@ -8,12 +8,12 @@ import ( "runtime" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/router" - "github.com/prebid/prebid-server/server" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/router" + "github.com/prebid/prebid-server/v2/server" + "github.com/prebid/prebid-server/v2/util/task" "github.com/golang/glog" "github.com/spf13/viper" diff --git a/main_test.go b/main_test.go index 25812ba96ab..79ae373d473 100644 --- a/main_test.go +++ b/main_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" "github.com/spf13/viper" diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 0cf0533d156..7ed387512fd 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -3,10 +3,10 @@ package config import ( "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - prometheusmetrics "github.com/prebid/prebid-server/metrics/prometheus" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + prometheusmetrics "github.com/prebid/prebid-server/v2/metrics/prometheus" + "github.com/prebid/prebid-server/v2/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" influxdb "github.com/vrischmann/go-metrics-influxdb" ) @@ -80,6 +80,12 @@ func (me *MultiMetricsEngine) RecordConnectionAccept(success bool) { } } +func (me *MultiMetricsEngine) RecordTMaxTimeout() { + for _, thisME := range *me { + thisME.RecordTMaxTimeout() + } +} + func (me *MultiMetricsEngine) RecordConnectionClose(success bool) { for _, thisME := range *me { thisME.RecordConnectionClose(success) @@ -316,27 +322,6 @@ func (me *MultiMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrt } } -func (me *MultiMetricsEngine) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - for _, thisME := range *me { - thisME.RecordAccountGDPRPurposeWarning(account, purposeName) - } -} -func (me *MultiMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { - for _, thisME := range *me { - thisME.RecordAccountGDPRChannelEnabledWarning(account) - } -} -func (me *MultiMetricsEngine) RecordAccountCCPAChannelEnabledWarning(account string) { - for _, thisME := range *me { - thisME.RecordAccountCCPAChannelEnabledWarning(account) - } -} -func (me *MultiMetricsEngine) RecordAccountUpgradeStatus(account string) { - for _, thisME := range *me { - thisME.RecordAccountUpgradeStatus(account) - } -} - func (me *MultiMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { for _, thisME := range *me { thisME.RecordModuleCalled(labels, duration) @@ -391,6 +376,10 @@ func (me *NilMetricsEngine) RecordRequest(labels metrics.Labels) { func (me *NilMetricsEngine) RecordConnectionAccept(success bool) { } +// RecordTMaxTimeout as a noop +func (me *NilMetricsEngine) RecordTMaxTimeout() { +} + // RecordConnectionClose as a noop func (me *NilMetricsEngine) RecordConnectionClose(success bool) { } @@ -526,18 +515,6 @@ func (me *NilMetricsEngine) RecordBidValidationSecureMarkupError(adapter openrtb func (me *NilMetricsEngine) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, account string) { } -func (me *NilMetricsEngine) RecordAccountGDPRPurposeWarning(account string, purposeName string) { -} - -func (me *NilMetricsEngine) RecordAccountGDPRChannelEnabledWarning(account string) { -} - -func (me *NilMetricsEngine) RecordAccountCCPAChannelEnabledWarning(account string) { -} - -func (me *NilMetricsEngine) RecordAccountUpgradeStatus(account string) { -} - func (me *NilMetricsEngine) RecordModuleCalled(labels metrics.ModuleLabels, duration time.Duration) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index f77dcf005d8..5badc348e61 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -2,12 +2,13 @@ package config import ( "fmt" + "strings" "testing" "time" - mainConfig "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + mainConfig "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" ) @@ -168,13 +169,14 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) - VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) - VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) + + VerifyMetrics(t, "AdapterMetrics.pubmatic.GotBidsMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].GotBidsMeter.Count(), 5) + VerifyMetrics(t, "AdapterMetrics.pubmatic.NoBidMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].NoBidMeter.Count(), 0) for _, err := range metrics.AdapterErrors() { - VerifyMetrics(t, "AdapterMetrics.Pubmatic.Request.ErrorMeter."+string(err), goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].ErrorMeters[err].Count(), 0) + VerifyMetrics(t, "AdapterMetrics.pubmatic.Request.ErrorMeter."+string(err), goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderPubmatic))].ErrorMeters[err].Count(), 0) } - VerifyMetrics(t, "AdapterMetrics.AppNexus.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GotBidsMeter.Count(), 0) - VerifyMetrics(t, "AdapterMetrics.AppNexus.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].NoBidMeter.Count(), 5) + VerifyMetrics(t, "AdapterMetrics.appnexus.GotBidsMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GotBidsMeter.Count(), 0) + VerifyMetrics(t, "AdapterMetrics.appnexus.NoBidMeter", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].NoBidMeter.Count(), 5) VerifyMetrics(t, "RecordRequestQueueTime.Video.Rejected", goEngine.RequestsQueueTimer[metrics.ReqTypeVideo][false].Count(), 1) VerifyMetrics(t, "RecordRequestQueueTime.Video.Accepted", goEngine.RequestsQueueTimer[metrics.ReqTypeVideo][true].Count(), 0) @@ -186,7 +188,7 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5) VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6) - VerifyMetrics(t, "AdapterMetrics.AppNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), 1) + VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1) // verify that each module has its own metric recorded for module, stages := range modulesStages { diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 64726e30fb6..5bc4ab965f6 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -2,18 +2,20 @@ package metrics import ( "fmt" + "strings" "sync" "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" metrics "github.com/rcrowley/go-metrics" ) type Metrics struct { MetricsRegistry metrics.Registry ConnectionCounter metrics.Counter + TMaxTimeoutCounter metrics.Counter ConnectionAcceptErrorMeter metrics.Meter ConnectionCloseErrorMeter metrics.Meter ImpMeter metrics.Meter @@ -61,12 +63,13 @@ type Metrics struct { PrivacyLMTRequest metrics.Meter PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter - AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + AdapterMetrics map[string]*AdapterMetrics // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics accountMetricsRWMutex sync.RWMutex - exchanges []openrtb_ext.BidderName + // adapter name exchanges + exchanges []string modules []string // Will hold boolean values to help us disable metric collection if needed MetricsDisabled config.DisabledMetrics @@ -116,7 +119,7 @@ type accountMetrics struct { bidsReceivedMeter metrics.Meter priceHistogram metrics.Histogram // store account by adapter metrics. Type is map[PBSBidder.BidderCode] - adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics + adapterMetrics map[string]*AdapterMetrics moduleMetrics map[string]*ModuleMetrics storedResponsesMeter metrics.Meter @@ -124,21 +127,6 @@ type accountMetrics struct { bidValidationCreativeSizeWarnMeter metrics.Meter bidValidationSecureMarkupMeter metrics.Meter bidValidationSecureMarkupWarnMeter metrics.Meter - - // Account Deprciation Metrics - accountDeprecationWarningsPurpose1Meter metrics.Meter - accountDeprecationWarningsPurpose2Meter metrics.Meter - accountDeprecationWarningsPurpose3Meter metrics.Meter - accountDeprecationWarningsPurpose4Meter metrics.Meter - accountDeprecationWarningsPurpose5Meter metrics.Meter - accountDeprecationWarningsPurpose6Meter metrics.Meter - accountDeprecationWarningsPurpose7Meter metrics.Meter - accountDeprecationWarningsPurpose8Meter metrics.Meter - accountDeprecationWarningsPurpose9Meter metrics.Meter - accountDeprecationWarningsPurpose10Meter metrics.Meter - channelEnabledGDPRMeter metrics.Meter - channelEnabledCCPAMeter metrics.Meter - accountDeprecationSummaryMeter metrics.Meter } type ModuleMetrics struct { @@ -159,7 +147,7 @@ type ModuleMetrics struct { // rather than loading metrics that never get filled. // This will also eventually let us configure metrics, such as setting a limited set of metrics // for a production instance, and then expanding again when we need more debugging. -func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics { +func NewBlankMetrics(registry metrics.Registry, exchanges []string, disabledMetrics config.DisabledMetrics, moduleStageNames map[string][]string) *Metrics { blankMeter := &metrics.NilMeter{} blankTimer := &metrics.NilTimer{} @@ -207,7 +195,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa PrivacyLMTRequest: blankMeter, PrivacyTCFRequestVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())), - AdapterMetrics: make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)), + AdapterMetrics: make(map[string]*AdapterMetrics, len(exchanges)), accountMetrics: make(map[string]*accountMetrics), MetricsDisabled: disabledMetrics, @@ -286,8 +274,13 @@ func getModuleNames(moduleStageNames map[string][]string) []string { // mode metrics. The code would allways try to record the metrics, but effectively noop if we are // using a blank meter/timer. func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableAccountMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics { - newMetrics := NewBlankMetrics(registry, exchanges, disableAccountMetrics, moduleStageNames) + lowerCaseExchanges := []string{} + for _, exchange := range exchanges { + lowerCaseExchanges = append(lowerCaseExchanges, strings.ToLower(string(exchange))) + } + newMetrics := NewBlankMetrics(registry, lowerCaseExchanges, disableAccountMetrics, moduleStageNames) newMetrics.ConnectionCounter = metrics.GetOrRegisterCounter("active_connections", registry) + newMetrics.TMaxTimeoutCounter = metrics.GetOrRegisterCounter("tmax_timeout", registry) newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) newMetrics.ImpMeter = metrics.GetOrRegisterMeter("imps_requested", registry) @@ -344,7 +337,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d } } - for _, a := range exchanges { + for _, a := range lowerCaseExchanges { registerAdapterMetrics(registry, "adapter", string(a), newMetrics.AdapterMetrics[a]) } @@ -559,7 +552,7 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.debugRequestMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.debug_requests", id), me.MetricsRegistry) am.bidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.bids_received", id), me.MetricsRegistry) am.priceHistogram = metrics.GetOrRegisterHistogram(fmt.Sprintf("account.%s.prices", id), me.MetricsRegistry, metrics.NewExpDecaySample(1028, 0.015)) - am.adapterMetrics = make(map[openrtb_ext.BidderName]*AdapterMetrics, len(me.exchanges)) + am.adapterMetrics = make(map[string]*AdapterMetrics, len(me.exchanges)) am.moduleMetrics = make(map[string]*ModuleMetrics) am.storedResponsesMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.stored_responses", id), me.MetricsRegistry) if !me.MetricsDisabled.AccountAdapterDetails { @@ -574,20 +567,6 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics { am.bidValidationSecureMarkupMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.err", id), me.MetricsRegistry) am.bidValidationSecureMarkupWarnMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.response.validation.secure.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose1Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose1.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose2Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose2.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose3Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose3.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose4Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose4.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose5Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose5.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose6Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose6.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose7Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose7.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose8Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose8.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose9Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose9.warn", id), me.MetricsRegistry) - am.accountDeprecationWarningsPurpose10Meter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.purpose10.warn", id), me.MetricsRegistry) - am.channelEnabledCCPAMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.ccpa.channel_enabled.warn", id), me.MetricsRegistry) - am.channelEnabledGDPRMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.gdpr.channel_enabled.warn", id), me.MetricsRegistry) - am.accountDeprecationSummaryMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("account.%s.config.summary", id), me.MetricsRegistry) - if !me.MetricsDisabled.AccountModulesMetrics { for _, mod := range me.modules { am.moduleMetrics[mod] = makeBlankModuleMetrics() @@ -633,55 +612,6 @@ func (me *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (me *Metrics) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - switch purposeName { - case "purpose1": - am.accountDeprecationWarningsPurpose1Meter.Mark(1) - case "purpose2": - am.accountDeprecationWarningsPurpose2Meter.Mark(1) - case "purpose3": - am.accountDeprecationWarningsPurpose3Meter.Mark(1) - case "purpose4": - am.accountDeprecationWarningsPurpose4Meter.Mark(1) - case "purpose5": - am.accountDeprecationWarningsPurpose5Meter.Mark(1) - case "purpose6": - am.accountDeprecationWarningsPurpose6Meter.Mark(1) - case "purpose7": - am.accountDeprecationWarningsPurpose7Meter.Mark(1) - case "purpose8": - am.accountDeprecationWarningsPurpose8Meter.Mark(1) - case "purpose9": - am.accountDeprecationWarningsPurpose9Meter.Mark(1) - case "purpose10": - am.accountDeprecationWarningsPurpose10Meter.Mark(1) - } - } -} - -func (me *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.channelEnabledGDPRMeter.Mark(1) - } -} - -func (me *Metrics) RecordAccountCCPAChannelEnabledWarning(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.channelEnabledCCPAMeter.Mark(1) - } -} - -func (me *Metrics) RecordAccountUpgradeStatus(account string) { - if account != PublisherUnknown { - am := me.getAccountMetrics(account) - am.accountDeprecationSummaryMeter.Mark(1) - } -} - func (me *Metrics) RecordStoredResponse(pubId string) { me.StoredResponsesMeter.Mark(1) if pubId != PublisherUnknown && !me.MetricsDisabled.AccountStoredResponses { @@ -713,6 +643,10 @@ func (me *Metrics) RecordConnectionAccept(success bool) { } } +func (m *Metrics) RecordTMaxTimeout() { + m.TMaxTimeoutCounter.Inc(1) +} + func (me *Metrics) RecordConnectionClose(success bool) { if success { me.ConnectionCounter.Dec(1) @@ -742,9 +676,11 @@ func (me *Metrics) RecordStoredDataError(labels StoredDataLabels) { // RecordAdapterPanic implements a part of the MetricsEngine interface func (me *Metrics) RecordAdapterPanic(labels AdapterLabels) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapterName := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.PanicMeter.Mark(1) @@ -752,13 +688,15 @@ func (me *Metrics) RecordAdapterPanic(labels AdapterLabels) { // RecordAdapterRequest implements a part of the MetricsEngine interface func (me *Metrics) RecordAdapterRequest(labels AdapterLabels) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapter] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } - aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter] + aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowerCaseAdapter] switch labels.AdapterBids { case AdapterBidNone: am.NoBidMeter.Mark(1) @@ -791,8 +729,8 @@ func (me *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, if me.MetricsDisabled.AdapterConnectionMetrics { return } - - am, ok := me.AdapterMetrics[adapterName] + lowerCaseAdapterName := strings.ToLower(string(adapterName)) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { glog.Errorf("Trying to log adapter connection metrics for %s: adapter not found", string(adapterName)) return @@ -821,16 +759,18 @@ func (me *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time. // RecordAdapterBidReceived implements a part of the MetricsEngine interface. // This tracks how many bids from each Bidder use `adm` vs. `nurl. func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowerCaseAdapterName := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowerCaseAdapterName] if !ok { - glog.Errorf("Trying to run adapter bid metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter bid metrics on %s: adapter metrics not found", adapterStr) return } // Adapter metrics am.BidsReceivedMeter.Mark(1) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowerCaseAdapterName]; ok { aam.BidsReceivedMeter.Mark(1) } @@ -847,22 +787,26 @@ func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrt // RecordAdapterPrice implements a part of the MetricsEngine interface. Generates a histogram of winning bid prices func (me *Metrics) RecordAdapterPrice(labels AdapterLabels, cpm float64) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowercaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowercaseAdapter] if !ok { - glog.Errorf("Trying to run adapter price metrics on %s: adapter metrics not found", string(labels.Adapter)) + glog.Errorf("Trying to run adapter price metrics on %s: adapter metrics not found", adapterStr) return } // Adapter metrics am.PriceHistogram.Update(int64(cpm)) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowercaseAdapter]; ok { aam.PriceHistogram.Update(int64(cpm)) } } // RecordAdapterTime implements a part of the MetricsEngine interface. Records the adapter response time func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) { - am, ok := me.AdapterMetrics[labels.Adapter] + adapterStr := string(labels.Adapter) + lowercaseAdapter := strings.ToLower(adapterStr) + am, ok := me.AdapterMetrics[lowercaseAdapter] if !ok { glog.Errorf("Trying to run adapter latency metrics on %s: adapter metrics not found", string(labels.Adapter)) return @@ -870,7 +814,7 @@ func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) // Adapter metrics am.RequestTimer.Update(length) // Account-Adapter metrics - if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[labels.Adapter]; ok { + if aam, ok := me.getAccountMetrics(labels.PubID).adapterMetrics[lowercaseAdapter]; ok { aam.RequestTimer.Update(length) } } @@ -983,13 +927,14 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { } func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + adapterStr := string(adapterName) if me.MetricsDisabled.AdapterGDPRRequestBlocked { return } - am, ok := me.AdapterMetrics[adapterName] + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", string(adapterName)) + glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", adapterStr) return } @@ -1009,9 +954,10 @@ func (me *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { } func (me *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationCreativeSizeErrorMeter.Mark(1) @@ -1023,9 +969,10 @@ func (me *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.Bidd } func (me *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationCreativeSizeWarnMeter.Mark(1) @@ -1037,9 +984,10 @@ func (me *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.Bidde } func (me *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationSecureMarkupErrorMeter.Mark(1) @@ -1051,9 +999,10 @@ func (me *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.Bidd } func (me *Metrics) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, pubID string) { - am, ok := me.AdapterMetrics[adapter] + adapterStr := string(adapter) + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] if !ok { - glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", string(adapter)) + glog.Errorf("Trying to run adapter metrics on %s: adapter metrics not found", adapterStr) return } am.BidValidationSecureMarkupWarnMeter.Mark(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 1ab22ac1a51..05529220f16 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" metrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) @@ -15,15 +15,15 @@ func TestNewMetrics(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} moduleStageNames := map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) ensureContains(t, registry, "app_requests", m.AppRequestMeter) ensureContains(t, registry, "debug_requests", m.DebugRequestMeter) ensureContains(t, registry, "no_cookie_requests", m.NoCookieMeter) ensureContains(t, registry, "request_time", m.RequestTimer) ensureContains(t, registry, "amp_no_cookie_requests", m.AmpNoCookieMeter) - ensureContainsAdapterMetrics(t, registry, "adapter.appnexus", m.AdapterMetrics["appnexus"]) - ensureContainsAdapterMetrics(t, registry, "adapter.rubicon", m.AdapterMetrics["rubicon"]) + ensureContainsAdapterMetrics(t, registry, "adapter.adapter1", m.AdapterMetrics["adapter1"]) + ensureContainsAdapterMetrics(t, registry, "adapter.adapter2", m.AdapterMetrics["adapter2"]) ensureContains(t, registry, "cookie_sync_requests", m.CookieSyncMeter) ensureContains(t, registry, "cookie_sync_requests.ok", m.CookieSyncStatusMeter[CookieSyncOK]) ensureContains(t, registry, "cookie_sync_requests.bad_request", m.CookieSyncStatusMeter[CookieSyncBadRequest]) @@ -84,6 +84,7 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "request_over_head_time.make-auction-response", m.OverheadTimer[MakeAuctionResponse]) ensureContains(t, registry, "request_over_head_time.make-bidder-requests", m.OverheadTimer[MakeBidderRequests]) ensureContains(t, registry, "bidder_server_response_time_seconds", m.BidderServerResponseTimer) + ensureContains(t, registry, "tmax_timeout", m.TMaxTimeoutCounter) for module, stages := range moduleStageNames { for _, stage := range stages { @@ -94,19 +95,21 @@ func TestNewMetrics(t *testing.T) { func TestRecordBidType(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + adapterName := "FOO" + lowerCaseAdapterName := "foo" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapterName)}, config.DisabledMetrics{}, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapterName), }, openrtb_ext.BidTypeBanner, true) - VerifyMetrics(t, "Appnexus Banner Adm Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeBanner].AdmMeter.Count(), 1) - VerifyMetrics(t, "Appnexus Banner Nurl Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeBanner].NurlMeter.Count(), 0) + VerifyMetrics(t, "foo Banner Adm Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeBanner].AdmMeter.Count(), 1) + VerifyMetrics(t, "foo Banner Nurl Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeBanner].NurlMeter.Count(), 0) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapterName), }, openrtb_ext.BidTypeVideo, false) - VerifyMetrics(t, "Appnexus Video Adm Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeVideo].AdmMeter.Count(), 0) - VerifyMetrics(t, "Appnexus Video Nurl Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeVideo].NurlMeter.Count(), 1) + VerifyMetrics(t, "foo Video Adm Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeVideo].AdmMeter.Count(), 0) + VerifyMetrics(t, "foo Video Nurl Bids", m.AdapterMetrics[lowerCaseAdapterName].MarkupMetrics[openrtb_ext.BidTypeVideo].NurlMeter.Count(), 1) } func ensureContains(t *testing.T, registry metrics.Registry, name string, metric interface{}) { @@ -197,17 +200,19 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { PubID: "acct-id", }, } - + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics, nil, nil) + + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.DisabledMetrics, nil, nil) m.RecordAdapterBidReceived(AdapterLabels{ - Adapter: openrtb_ext.BidderAppnexus, + Adapter: openrtb_ext.BidderName(adapter), PubID: test.PubID, }, test.BidType, test.hasAdm) - assert.Equal(t, test.ExpectedAdmMeterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[test.BidType].AdmMeter.Count(), "Appnexus Banner Adm Bids") - assert.Equal(t, test.ExpectedNurlMeterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[test.BidType].NurlMeter.Count(), "Appnexus Banner Nurl Bids") + assert.Equal(t, test.ExpectedAdmMeterCount, m.AdapterMetrics[lowerCaseAdapter].MarkupMetrics[test.BidType].AdmMeter.Count(), "AnyName Banner Adm Bids") + assert.Equal(t, test.ExpectedNurlMeterCount, m.AdapterMetrics[lowerCaseAdapter].MarkupMetrics[test.BidType].NurlMeter.Count(), "AnyName Banner Nurl Bids") if test.DisabledMetrics.AccountAdapterDetails { assert.Len(t, m.accountMetrics[test.PubID].adapterMetrics, 0, "Test failed. Account metrics that contain adapter information are disabled, therefore we expect no entries in m.accountMetrics[accountId].adapterMetrics, we have %d \n", len(m.accountMetrics[test.PubID].adapterMetrics)) @@ -271,9 +276,10 @@ func TestRecordDebugRequest(t *testing.T) { expectedDebugCount: 0, }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) m.RecordDebugRequest(test.givenDebugEnabledFlag, test.givenPubID) am := m.getAccountMetrics(test.givenPubID) @@ -310,16 +316,18 @@ func TestRecordBidValidationCreativeSize(t *testing.T) { expectedAccountCount: 0, }, } + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) - m.RecordBidValidationCreativeSizeError(openrtb_ext.BidderAppnexus, test.givenPubID) - m.RecordBidValidationCreativeSizeWarn(openrtb_ext.BidderAppnexus, test.givenPubID) + m.RecordBidValidationCreativeSizeError(openrtb_ext.BidderName(adapter), test.givenPubID) + m.RecordBidValidationCreativeSizeWarn(openrtb_ext.BidderName(adapter), test.givenPubID) am := m.getAccountMetrics(test.givenPubID) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationCreativeSizeErrorMeter.Count()) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationCreativeSizeWarnMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationCreativeSizeErrorMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationCreativeSizeWarnMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationCreativeSizeMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationCreativeSizeWarnMeter.Count()) } @@ -352,16 +360,18 @@ func TestRecordBidValidationSecureMarkup(t *testing.T) { expectedAccountCount: 0, }, } + adapter := "AnyName" + lowerCaseAdapter := "anyname" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.givenDisabledMetrics, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, test.givenDisabledMetrics, nil, nil) - m.RecordBidValidationSecureMarkupError(openrtb_ext.BidderAppnexus, test.givenPubID) - m.RecordBidValidationSecureMarkupWarn(openrtb_ext.BidderAppnexus, test.givenPubID) + m.RecordBidValidationSecureMarkupError(openrtb_ext.BidderName(adapter), test.givenPubID) + m.RecordBidValidationSecureMarkupWarn(openrtb_ext.BidderName(adapter), test.givenPubID) am := m.getAccountMetrics(test.givenPubID) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationSecureMarkupErrorMeter.Count()) - assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].BidValidationSecureMarkupWarnMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationSecureMarkupErrorMeter.Count()) + assert.Equal(t, test.expectedAdapterCount, m.AdapterMetrics[lowerCaseAdapter].BidValidationSecureMarkupWarnMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationSecureMarkupMeter.Count()) assert.Equal(t, test.expectedAccountCount, am.bidValidationSecureMarkupWarnMeter.Count()) } @@ -384,9 +394,10 @@ func TestRecordDNSTime(t *testing.T) { outExpDuration: time.Duration(0), }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordDNSTime(test.inDnsLookupDuration) @@ -411,9 +422,10 @@ func TestRecordTLSHandshakeTime(t *testing.T) { expectedDuration: time.Duration(0), }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) @@ -441,9 +453,10 @@ func TestRecordBidderServerResponseTime(t *testing.T) { expectedSum: 1000, }, } + adapter := "AnyName" for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordBidderServerResponseTime(test.time) @@ -466,7 +479,8 @@ func TestRecordAdapterConnections(t *testing.T) { expectedConnCreatedCount int64 expectedConnWaitTime time.Duration } - + adapter := "AnyName" + lowerCaseAdapterName := "anyname" testCases := []struct { description string in testIn @@ -475,7 +489,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, new connection created, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 5, connMetricsDisabled: false, @@ -489,7 +503,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, new connection created, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 4, connMetricsDisabled: false, @@ -502,7 +516,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, was reused, no connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: true, connMetricsDisabled: false, }, @@ -514,7 +528,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Successful, was reused, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: true, connWait: time.Second * 5, connMetricsDisabled: false, @@ -537,7 +551,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "Adapter connection metrics are disabled, nothing gets updated", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), connWasReused: false, connWait: time.Second * 5, connMetricsDisabled: true, @@ -548,19 +562,18 @@ func TestRecordAdapterConnections(t *testing.T) { for i, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil, nil) m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) - - assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i) - assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i) - assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i) + assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[lowerCaseAdapterName].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i) + assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[lowerCaseAdapterName].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i) + assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[lowerCaseAdapterName].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i) } } func TestNewMetricsWithDisabledConfig(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("bar")}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) assert.True(t, m.MetricsDisabled.AccountAdapterDetails, "Accound adapter metrics should be disabled") assert.True(t, m.MetricsDisabled.AccountModulesMetrics, "Accound modules metrics should be disabled") @@ -568,7 +581,7 @@ func TestNewMetricsWithDisabledConfig(t *testing.T) { func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(true, 42) @@ -578,7 +591,7 @@ func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordPrebidCacheRequestTime(false, 42) @@ -646,7 +659,7 @@ func TestRecordStoredDataFetchTime(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataFetchTime(StoredDataLabels{ DataType: tt.dataType, DataFetchType: tt.fetchType, @@ -720,7 +733,7 @@ func TestRecordStoredDataError(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) m.RecordStoredDataError(StoredDataLabels{ DataType: tt.dataType, Error: tt.errorType, @@ -733,7 +746,7 @@ func TestRecordStoredDataError(t *testing.T) { func TestRecordRequestPrivacy(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{AccountAdapterDetails: true}, nil, nil) // CCPA m.RecordRequestPrivacy(PrivacyLabels{ @@ -779,6 +792,8 @@ func TestRecordRequestPrivacy(t *testing.T) { func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + adapter := "AnyName" + lowerCaseAdapterName := "anyname" tests := []struct { description string @@ -789,7 +804,7 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { { description: "", metricsDisabled: false, - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), expectedCount: 1, }, { @@ -801,24 +816,23 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { { description: "", metricsDisabled: true, - adapterName: openrtb_ext.BidderAppnexus, + adapterName: openrtb_ext.BidderName(adapter), expectedCount: 0, }, } - for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) m.RecordAdapterGDPRRequestBlocked(tt.adapterName) - assert.Equal(t, tt.expectedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), tt.description) + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count(), tt.description) } } func TestRecordCookieSync(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{}, nil, nil) // Known m.RecordCookieSync(CookieSyncBadRequest) @@ -836,7 +850,7 @@ func TestRecordCookieSync(t *testing.T) { func TestRecordSyncerRequest(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerRequest("foo", SyncerCookieSyncOK) @@ -855,7 +869,7 @@ func TestRecordSyncerRequest(t *testing.T) { func TestRecordSetUid(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Foo"), openrtb_ext.BidderName("Bar")}, config.DisabledMetrics{}, nil, nil) // Known m.RecordSetUid(SetUidOptOut) @@ -874,7 +888,7 @@ func TestRecordSetUid(t *testing.T) { func TestRecordSyncerSet(t *testing.T) { registry := metrics.NewRegistry() syncerKeys := []string{"foo"} - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("Adapter1"), openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) // Known m.RecordSyncerSet("foo", SyncerSetUidCleared) @@ -928,7 +942,7 @@ func TestStoredResponses(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountStoredResponses: test.accountStoredResponsesMetricsDisabled}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{AccountStoredResponses: test.accountStoredResponsesMetricsDisabled}, nil, nil) m.RecordStoredResponse(test.givenPubID) am := m.getAccountMetrics(test.givenPubID) @@ -962,7 +976,7 @@ func TestRecordAdsCertSignTime(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{}, nil, nil) m.RecordAdsCertSignTime(test.inAdsCertSignDuration) @@ -993,7 +1007,7 @@ func TestRecordAdsCertReqMetric(t *testing.T) { for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName("AnyName")}, config.DisabledMetrics{}, nil, nil) m.RecordAdsCertReq(test.requestSuccess) @@ -1065,169 +1079,6 @@ func TestRecordModuleAccountMetrics(t *testing.T) { } } -func TestRecordAccountGDPRPurposeWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPurposeName string - expectedP1MetricCount int64 - expectedP2MetricCount int64 - expectedP3MetricCount int64 - expectedP4MetricCount int64 - expectedP5MetricCount int64 - expectedP6MetricCount int64 - expectedP7MetricCount int64 - expectedP8MetricCount int64 - expectedP9MetricCount int64 - expectedP10MetricCount int64 - }{ - { - name: "Purpose1MetricIncremented", - givenPurposeName: "purpose1", - expectedP1MetricCount: 1, - }, - { - name: "Purpose2MetricIncremented", - givenPurposeName: "purpose2", - expectedP2MetricCount: 1, - }, - { - name: "Purpose3MetricIncremented", - givenPurposeName: "purpose3", - expectedP3MetricCount: 1, - }, - { - name: "Purpose4MetricIncremented", - givenPurposeName: "purpose4", - expectedP4MetricCount: 1, - }, - { - name: "Purpose5MetricIncremented", - givenPurposeName: "purpose5", - expectedP5MetricCount: 1, - }, - { - name: "Purpose6MetricIncremented", - givenPurposeName: "purpose6", - expectedP6MetricCount: 1, - }, - { - name: "Purpose7MetricIncremented", - givenPurposeName: "purpose7", - expectedP7MetricCount: 1, - }, - { - name: "Purpose8MetricIncremented", - givenPurposeName: "purpose8", - expectedP8MetricCount: 1, - }, - { - name: "Purpose9MetricIncremented", - givenPurposeName: "purpose9", - expectedP9MetricCount: 1, - }, - { - name: "Purpose10MetricIncremented", - givenPurposeName: "purpose10", - expectedP10MetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountGDPRPurposeWarning("acct-id", test.givenPurposeName) - am := m.getAccountMetrics("acct-id") - - assert.Equal(t, test.expectedP1MetricCount, am.accountDeprecationWarningsPurpose1Meter.Count()) - assert.Equal(t, test.expectedP2MetricCount, am.accountDeprecationWarningsPurpose2Meter.Count()) - assert.Equal(t, test.expectedP3MetricCount, am.accountDeprecationWarningsPurpose3Meter.Count()) - assert.Equal(t, test.expectedP4MetricCount, am.accountDeprecationWarningsPurpose4Meter.Count()) - assert.Equal(t, test.expectedP5MetricCount, am.accountDeprecationWarningsPurpose5Meter.Count()) - assert.Equal(t, test.expectedP6MetricCount, am.accountDeprecationWarningsPurpose6Meter.Count()) - assert.Equal(t, test.expectedP7MetricCount, am.accountDeprecationWarningsPurpose7Meter.Count()) - assert.Equal(t, test.expectedP8MetricCount, am.accountDeprecationWarningsPurpose8Meter.Count()) - assert.Equal(t, test.expectedP9MetricCount, am.accountDeprecationWarningsPurpose9Meter.Count()) - assert.Equal(t, test.expectedP10MetricCount, am.accountDeprecationWarningsPurpose10Meter.Count()) - }) - } -} - -func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "GdprChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountGDPRChannelEnabledWarning(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.channelEnabledGDPRMeter.Count()) - }) - } -} - -func TestRecordAccountCCPAChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "CcpaChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountCCPAChannelEnabledWarning(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.channelEnabledCCPAMeter.Count()) - }) - } -} - -func TestRecordAccountUpgradeStatusMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount int64 - }{ - { - name: "AccountDeprecationMeterIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil, nil) - - m.RecordAccountUpgradeStatus(test.givenPubID) - am := m.getAccountMetrics(test.givenPubID) - - assert.Equal(t, test.expectedMetricCount, am.accountDeprecationSummaryMeter.Count()) - }) - } -} - func TestRecordOverheadTime(t *testing.T) { testCases := []struct { name string @@ -1293,3 +1144,127 @@ func VerifyMetrics(t *testing.T, name string, expected int64, actual int64) { t.Errorf("Error in metric %s: expected %d, got %d.", name, expected, actual) } } + +func TestRecordAdapterPanic(t *testing.T) { + registry := metrics.NewRegistry() + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AccountAdapterDetails: true, AccountModulesMetrics: true}, nil, map[string][]string{"foobar": {"entry", "raw"}}) + m.RecordAdapterPanic(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter)}) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].PanicMeter.Count(), int64(1)) +} + +func TestRecordAdapterPrice(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + pubID := "pub1" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter), openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, syncerKeys, nil) + m.RecordAdapterPrice(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter), PubID: pubID}, 1000) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].PriceHistogram.Max(), int64(1000)) + assert.Equal(t, m.getAccountMetrics(pubID).adapterMetrics[lowerCaseAdapterName].PriceHistogram.Max(), int64(1000)) +} + +func TestRecordAdapterTime(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + pubID := "pub1" + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter), openrtb_ext.BidderAppnexus, openrtb_ext.BidderName("Adapter2")}, config.DisabledMetrics{}, syncerKeys, nil) + m.RecordAdapterTime(AdapterLabels{Adapter: openrtb_ext.BidderName(adapter), PubID: pubID}, 1000) + assert.Equal(t, m.AdapterMetrics[lowerCaseAdapterName].RequestTimer.Max(), int64(1000)) + assert.Equal(t, m.getAccountMetrics(pubID).adapterMetrics[lowerCaseAdapterName].RequestTimer.Max(), int64(1000)) +} + +func TestRecordAdapterRequest(t *testing.T) { + syncerKeys := []string{"foo"} + moduleStageNames := map[string][]string{"foobar": {"entry", "raw"}, "another_module": {"raw", "auction"}} + adapter := "AnyName" + lowerCaseAdapter := "anyname" + type errorCount struct { + badInput, badServer, timeout, failedToRequestBid, validation, tmaxTimeout, unknown int64 + } + type adapterBidsCount struct { + NoBid, GotBid int64 + } + tests := []struct { + description string + labels AdapterLabels + expectedNoCookieCount int64 + expectedAdapterBidsCount adapterBidsCount + expectedErrorCount errorCount + }{ + { + description: "no-bid", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + AdapterBids: AdapterBidNone, + PubID: "acc-1", + }, + expectedAdapterBidsCount: adapterBidsCount{NoBid: 1}, + }, + { + description: "got-bid", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + AdapterBids: AdapterBidPresent, + PubID: "acc-2", + }, + expectedAdapterBidsCount: adapterBidsCount{GotBid: 1}, + }, + { + description: "adapter-errors", + labels: AdapterLabels{ + Adapter: openrtb_ext.BidderName(adapter), + PubID: "acc-1", + AdapterErrors: map[AdapterError]struct{}{ + AdapterErrorBadInput: {}, + AdapterErrorBadServerResponse: {}, + AdapterErrorFailedToRequestBids: {}, + AdapterErrorTimeout: {}, + AdapterErrorValidation: {}, + AdapterErrorTmaxTimeout: {}, + AdapterErrorUnknown: {}, + }, + }, + expectedErrorCount: errorCount{ + badInput: 1, + badServer: 1, + timeout: 1, + failedToRequestBid: 1, + validation: 1, + tmaxTimeout: 1, + unknown: 1, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{}, syncerKeys, moduleStageNames) + m.RecordAdapterRequest(test.labels) + adapterMetric := m.AdapterMetrics[lowerCaseAdapter] + if assert.NotNil(t, adapterMetric) { + assert.Equal(t, test.expectedAdapterBidsCount, adapterBidsCount{ + NoBid: adapterMetric.NoBidMeter.Count(), + GotBid: adapterMetric.GotBidsMeter.Count(), + }) + } + assert.Equal(t, test.expectedNoCookieCount, adapterMetric.NoCookieMeter.Count()) + adapterErrMetric := adapterMetric.ErrorMeters + if assert.NotNil(t, adapterErrMetric) { + assert.Equal(t, test.expectedErrorCount, errorCount{ + badInput: adapterErrMetric[AdapterErrorBadInput].Count(), + badServer: adapterErrMetric[AdapterErrorBadServerResponse].Count(), + timeout: adapterErrMetric[AdapterErrorTimeout].Count(), + failedToRequestBid: adapterErrMetric[AdapterErrorFailedToRequestBids].Count(), + validation: adapterErrMetric[AdapterErrorValidation].Count(), + tmaxTimeout: adapterErrMetric[AdapterErrorTmaxTimeout].Count(), + unknown: adapterErrMetric[AdapterErrorUnknown].Count(), + }) + } + }) + } +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 214e7c1ddcb..7d3dc819341 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // Labels defines the labels that can be attached to the metrics. @@ -164,6 +164,7 @@ const PublisherUnknown = "unknown" const ( DemandWeb DemandSource = "web" DemandApp DemandSource = "app" + DemandDOOH DemandSource = "dooh" DemandUnknown DemandSource = "unknown" ) @@ -171,22 +172,25 @@ func DemandTypes() []DemandSource { return []DemandSource{ DemandWeb, DemandApp, + DemandDOOH, DemandUnknown, } } // The request types (endpoints) const ( - ReqTypeORTB2Web RequestType = "openrtb2-web" - ReqTypeORTB2App RequestType = "openrtb2-app" - ReqTypeAMP RequestType = "amp" - ReqTypeVideo RequestType = "video" + ReqTypeORTB2Web RequestType = "openrtb2-web" + ReqTypeORTB2App RequestType = "openrtb2-app" + ReqTypeORTB2DOOH RequestType = "openrtb2-dooh" + ReqTypeAMP RequestType = "amp" + ReqTypeVideo RequestType = "video" ) func RequestTypes() []RequestType { return []RequestType{ ReqTypeORTB2Web, ReqTypeORTB2App, + ReqTypeORTB2DOOH, ReqTypeAMP, ReqTypeVideo, } @@ -267,6 +271,7 @@ const ( AdapterErrorTimeout AdapterError = "timeout" AdapterErrorFailedToRequestBids AdapterError = "failedtorequestbid" AdapterErrorValidation AdapterError = "validation" + AdapterErrorTmaxTimeout AdapterError = "tmaxtimeout" AdapterErrorUnknown AdapterError = "unknown_error" ) @@ -277,6 +282,7 @@ func AdapterErrors() []AdapterError { AdapterErrorTimeout, AdapterErrorFailedToRequestBids, AdapterErrorValidation, + AdapterErrorTmaxTimeout, AdapterErrorUnknown, } } @@ -421,6 +427,7 @@ func SyncerSetUidStatuses() []SyncerSetUidStatus { // is generally not useful. type MetricsEngine interface { RecordConnectionAccept(success bool) + RecordTMaxTimeout() RecordConnectionClose(success bool) RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status RecordImps(labels ImpLabels) // RecordImps across openRTB2 engines that support the 'Native' Imp Type @@ -464,8 +471,4 @@ type MetricsEngine interface { RecordModuleSuccessRejected(labels ModuleLabels) RecordModuleExecutionError(labels ModuleLabels) RecordModuleTimeout(labels ModuleLabels) - RecordAccountGDPRPurposeWarning(account string, purposeName string) - RecordAccountGDPRChannelEnabledWarning(account string) - RecordAccountCCPAChannelEnabledWarning(account string) - RecordAccountUpgradeStatus(account string) } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 0ce238c582d..ef9fdcaf7a4 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/mock" ) @@ -22,6 +22,11 @@ func (me *MetricsEngineMock) RecordConnectionAccept(success bool) { me.Called(success) } +// RecordTMaxTimeout mock +func (me *MetricsEngineMock) RecordTMaxTimeout() { + me.Called() +} + // RecordConnectionClose mock func (me *MetricsEngineMock) RecordConnectionClose(success bool) { me.Called(success) @@ -216,19 +221,3 @@ func (me *MetricsEngineMock) RecordModuleExecutionError(labels ModuleLabels) { func (me *MetricsEngineMock) RecordModuleTimeout(labels ModuleLabels) { me.Called(labels) } - -func (me *MetricsEngineMock) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - me.Called(account, purposeName) -} - -func (me *MetricsEngineMock) RecordAccountGDPRChannelEnabledWarning(account string) { - me.Called(account) -} - -func (me *MetricsEngineMock) RecordAccountCCPAChannelEnabledWarning(account string) { - me.Called(account) -} - -func (me *MetricsEngineMock) RecordAccountUpgradeStatus(account string) { - me.Called(account) -} diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index dae7c14dc5b..a4a70017355 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -1,29 +1,31 @@ package prometheusmetrics import ( - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[string][]string) { var ( - setUidStatusValues = setUidStatusesAsString() - adapterErrorValues = adapterErrorsAsString() - adapterValues = adaptersAsString() + adapterErrorValues = enumAsString(metrics.AdapterErrors()) + adapterValues = enumAsLowerCaseString(openrtb_ext.CoreBidderNames()) bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} boolValues = boolValuesAsString() - cacheResultValues = cacheResultsAsString() + cacheResultValues = enumAsString(metrics.CacheResults()) connectionErrorValues = []string{connectionAcceptError, connectionCloseError} - cookieValues = cookieTypesAsString() - cookieSyncStatusValues = cookieSyncStatusesAsString() - overheadTypes = overheadTypesAsString() - requestTypeValues = requestTypesAsString() - requestStatusValues = requestStatusesAsString() - storedDataFetchTypeValues = storedDataFetchTypesAsString() - storedDataErrorValues = storedDataErrorsAsString() - syncerRequestStatusValues = syncerRequestStatusesAsString() - syncerSetsStatusValues = syncerSetStatusesAsString() + cookieSyncStatusValues = enumAsString(metrics.CookieSyncStatuses()) + cookieValues = enumAsString(metrics.CookieTypes()) + overheadTypes = enumAsString(metrics.OverheadTypes()) + requestStatusValues = enumAsString(metrics.RequestStatuses()) + requestTypeValues = enumAsString(metrics.RequestTypes()) + setUidStatusValues = enumAsString(metrics.SetUidStatuses()) sourceValues = []string{sourceRequest} + storedDataErrorValues = enumAsString(metrics.StoredDataErrors()) + storedDataFetchTypeValues = enumAsString(metrics.StoredDataFetchTypes()) + syncerRequestStatusValues = enumAsString(metrics.SyncerRequestStatuses()) + syncerSetsStatusValues = enumAsString(metrics.SyncerSetUidStatuses()) + tcfVersionValues = enumAsString(metrics.TCFVersions()) ) preloadLabelValuesForCounter(m.connectionsError, map[string][]string{ @@ -224,7 +226,7 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st preloadLabelValuesForCounter(m.privacyTCF, map[string][]string{ sourceLabel: sourceValues, - versionLabel: tcfVersionsAsString(), + versionLabel: tcfVersionValues, }) if !m.metricsDisabled.AdapterGDPRRequestBlocked { diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index eb387789e1d..6bd30544662 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -3,11 +3,12 @@ package prometheusmetrics import ( "fmt" "strconv" + "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" promCollector "github.com/prometheus/client_golang/prometheus/collectors" ) @@ -18,6 +19,7 @@ type Metrics struct { Gatherer *prometheus.Registry // General Metrics + tmaxTimeout prometheus.Counter connectionsClosed prometheus.Counter connectionsError *prometheus.CounterVec connectionsOpened prometheus.Counter @@ -87,21 +89,6 @@ type Metrics struct { accountBidResponseSecureMarkupError *prometheus.CounterVec accountBidResponseSecureMarkupWarn *prometheus.CounterVec - // Account Deprecation Metrics - accountDeprecationWarningsPurpose1 prometheus.Counter - accountDeprecationWarningsPurpose2 prometheus.Counter - accountDeprecationWarningsPurpose3 prometheus.Counter - accountDeprecationWarningsPurpose4 prometheus.Counter - accountDeprecationWarningsPurpose5 prometheus.Counter - accountDeprecationWarningsPurpose6 prometheus.Counter - accountDeprecationWarningsPurpose7 prometheus.Counter - accountDeprecationWarningsPurpose8 prometheus.Counter - accountDeprecationWarningsPurpose9 prometheus.Counter - accountDeprecationWarningsPurpose10 prometheus.Counter - channelEnabledGDPR prometheus.Counter - channelEnabledCCPA prometheus.Counter - accountDeprecationSummary prometheus.Counter - // Module Metrics as a map where the key is the module name moduleDuration map[string]*prometheus.HistogramVec moduleCalls map[string]*prometheus.CounterVec @@ -178,7 +165,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300} - overheadTimeBuckets := []float64{0.00005, 0.0001, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.05} + overheadTimeBuckets := []float64{0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1} metrics := Metrics{} reg := prometheus.NewRegistry() @@ -197,6 +184,10 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "connections_opened", "Count of successful connections opened to Prebid Server.") + metrics.tmaxTimeout = newCounterWithoutLabels(cfg, reg, + "tmax_timeout", + "Count of requests rejected due to Tmax timeout exceed.") + metrics.cookieSync = newCounter(cfg, reg, "cookie_sync_requests", "Count of cookie sync requests to Prebid Server.", @@ -507,48 +498,6 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of AdsCert request, and if they were successfully sent.", []string{successLabel}) - metrics.accountDeprecationWarningsPurpose1 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose1_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose1 field") - metrics.accountDeprecationWarningsPurpose2 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose2_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose2 field") - metrics.accountDeprecationWarningsPurpose3 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose3_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose3 field") - metrics.accountDeprecationWarningsPurpose4 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose4_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose4 field") - metrics.accountDeprecationWarningsPurpose5 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose5_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose5 field") - metrics.accountDeprecationWarningsPurpose6 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose6_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose6 field") - metrics.accountDeprecationWarningsPurpose7 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose7_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose7 field") - metrics.accountDeprecationWarningsPurpose8 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose8_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose8 field") - metrics.accountDeprecationWarningsPurpose9 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose9_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose9 field") - metrics.accountDeprecationWarningsPurpose10 = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_tcf2_purpose10_warn", - "Count of requests referencing an account whose config specifies a deprecated gdpr.tcf2.purpose10 field") - - metrics.channelEnabledCCPA = newCounterWithoutLabels(cfg, reg, - "account_config_ccpa_channel_enabled_warn", - "Count of requests referencing an account whose config specifies a depreceated ccpa.channel_enabled field") - metrics.channelEnabledGDPR = newCounterWithoutLabels(cfg, reg, - "account_config_gdpr_channel_enabled_warn", - "Count of requests referencing an account whose config specifies a depreceated gdpr.channel_enabled field") - - metrics.accountDeprecationSummary = newCounterWithoutLabels(cfg, reg, - "account_config_summary", - "Count of deprecated account config fields encountered across all accounts") - createModulesMetrics(cfg, reg, &metrics, moduleStageNames, standardTimeBuckets) metrics.Gatherer = reg @@ -685,6 +634,10 @@ func (m *Metrics) RecordConnectionAccept(success bool) { } } +func (m *Metrics) RecordTMaxTimeout() { + m.tmaxTimeout.Inc() +} + func (m *Metrics) RecordConnectionClose(success bool) { if success { m.connectionsClosed.Inc() @@ -725,51 +678,6 @@ func (m *Metrics) RecordDebugRequest(debugEnabled bool, pubID string) { } } -func (m *Metrics) RecordAccountGDPRPurposeWarning(account string, purposeName string) { - if account != metrics.PublisherUnknown { - switch purposeName { - case "purpose1": - m.accountDeprecationWarningsPurpose1.Inc() - case "purpose2": - m.accountDeprecationWarningsPurpose2.Inc() - case "purpose3": - m.accountDeprecationWarningsPurpose3.Inc() - case "purpose4": - m.accountDeprecationWarningsPurpose4.Inc() - case "purpose5": - m.accountDeprecationWarningsPurpose5.Inc() - case "purpose6": - m.accountDeprecationWarningsPurpose6.Inc() - case "purpose7": - m.accountDeprecationWarningsPurpose7.Inc() - case "purpose8": - m.accountDeprecationWarningsPurpose8.Inc() - case "purpose9": - m.accountDeprecationWarningsPurpose9.Inc() - case "purpose10": - m.accountDeprecationWarningsPurpose10.Inc() - } - } -} - -func (m *Metrics) RecordAccountGDPRChannelEnabledWarning(account string) { - if account != metrics.PublisherUnknown { - m.channelEnabledGDPR.Inc() - } -} - -func (m *Metrics) RecordAccountCCPAChannelEnabledWarning(account string) { - if account != metrics.PublisherUnknown { - m.channelEnabledCCPA.Inc() - } -} - -func (m *Metrics) RecordAccountUpgradeStatus(account string) { - if account != metrics.PublisherUnknown { - m.accountDeprecationSummary.Inc() - } -} - func (m *Metrics) RecordStoredResponse(pubId string) { m.storedResponses.Inc() if !m.metricsDisabled.AccountStoredResponses && pubId != metrics.PublisherUnknown { @@ -855,15 +763,16 @@ func (m *Metrics) RecordStoredDataError(labels metrics.StoredDataLabels) { } func (m *Metrics) RecordAdapterRequest(labels metrics.AdapterLabels) { + lowerCasedAdapter := strings.ToLower(string(labels.Adapter)) m.adapterRequests.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: lowerCasedAdapter, cookieLabel: string(labels.CookieFlag), hasBidsLabel: strconv.FormatBool(labels.AdapterBids == metrics.AdapterBidPresent), }).Inc() for err := range labels.AdapterErrors { m.adapterErrors.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: lowerCasedAdapter, adapterErrorLabel: string(err), }).Inc() } @@ -872,22 +781,23 @@ func (m *Metrics) RecordAdapterRequest(labels metrics.AdapterLabels) { // Keeps track of created and reused connections to adapter bidders and the time from the // connection request, to the connection creation, or reuse from the pool across all engines func (m *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { + lowerCasedAdapterName := strings.ToLower(string(adapterName)) if m.metricsDisabled.AdapterConnectionMetrics { return } if connWasReused { m.adapterReusedConnections.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Inc() } else { m.adapterCreatedConnections.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Inc() } m.adapterConnectionWaitTime.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: lowerCasedAdapterName, }).Observe(connWaitTime.Seconds()) } @@ -905,7 +815,7 @@ func (m *Metrics) RecordBidderServerResponseTime(bidderServerResponseTime time.D func (m *Metrics) RecordAdapterPanic(labels metrics.AdapterLabels) { m.adapterPanics.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Inc() } @@ -916,14 +826,14 @@ func (m *Metrics) RecordAdapterBidReceived(labels metrics.AdapterLabels, bidType } m.adapterBids.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), markupDeliveryLabel: markupDelivery, }).Inc() } func (m *Metrics) RecordAdapterPrice(labels metrics.AdapterLabels, cpm float64) { m.adapterPrices.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Observe(cpm) } @@ -936,7 +846,7 @@ func (m *Metrics) RecordOverheadTime(overhead metrics.OverheadType, duration tim func (m *Metrics) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) { if len(labels.AdapterErrors) == 0 { m.adapterRequestsTimer.With(prometheus.Labels{ - adapterLabel: string(labels.Adapter), + adapterLabel: strings.ToLower(string(labels.Adapter)), }).Observe(length.Seconds()) } } @@ -1048,7 +958,7 @@ func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidder } m.adapterGDPRBlockedRequests.With(prometheus.Labels{ - adapterLabel: string(adapterName), + adapterLabel: strings.ToLower(string(adapterName)), }).Inc() } @@ -1068,8 +978,9 @@ func (m *Metrics) RecordAdsCertSignTime(adsCertSignTime time.Duration) { } func (m *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.BidderName, account string) { + lowerCasedAdapter := strings.ToLower(string(adapter)) m.adapterBidResponseValidationSizeError.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: lowerCasedAdapter, successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -1080,8 +991,9 @@ func (m *Metrics) RecordBidValidationCreativeSizeError(adapter openrtb_ext.Bidde } func (m *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.BidderName, account string) { + lowerCasedAdapter := strings.ToLower(string(adapter)) m.adapterBidResponseValidationSizeWarn.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: lowerCasedAdapter, successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -1093,7 +1005,7 @@ func (m *Metrics) RecordBidValidationCreativeSizeWarn(adapter openrtb_ext.Bidder func (m *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.BidderName, account string) { m.adapterBidResponseSecureMarkupError.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: strings.ToLower(string(adapter)), successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { @@ -1105,7 +1017,7 @@ func (m *Metrics) RecordBidValidationSecureMarkupError(adapter openrtb_ext.Bidde func (m *Metrics) RecordBidValidationSecureMarkupWarn(adapter openrtb_ext.BidderName, account string) { m.adapterBidResponseSecureMarkupWarn.With(prometheus.Labels{ - adapterLabel: string(adapter), successLabel: successLabel, + adapterLabel: strings.ToLower(string(adapter)), successLabel: successLabel, }).Inc() if !m.metricsDisabled.AccountAdapterDetails && account != metrics.PublisherUnknown { diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 32f7848ccff..a74c8b6c0fa 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" @@ -64,7 +64,7 @@ func TestMetricCountGatekeeping(t *testing.T) { // Verify Per-Adapter Cardinality // - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the // cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics. - assert.True(t, perAdapterCardinalityCount <= 29, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) + assert.True(t, perAdapterCardinalityCount <= 30, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) } func TestConnectionMetrics(t *testing.T) { @@ -226,18 +226,19 @@ func TestBidValidationCreativeSizeMetric(t *testing.T) { expectedAccountCount: 0, }, } - + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" for _, test := range testCases { m := createMetricsForTesting() m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled - m.RecordBidValidationCreativeSizeError(adapterLabel, "acct-id") - m.RecordBidValidationCreativeSizeWarn(adapterLabel, "acct-id") + m.RecordBidValidationCreativeSizeError(adapterName, "acct-id") + m.RecordBidValidationCreativeSizeWarn(adapterName, "acct-id") assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) assertCounterVecValue(t, "", "account bid validation", m.accountBidResponseValidationSizeWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "adapter bid validation", m.adapterBidResponseValidationSizeWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) } } @@ -263,17 +264,19 @@ func TestBidValidationSecureMarkupMetric(t *testing.T) { }, } + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" for _, test := range testCases { m := createMetricsForTesting() m.metricsDisabled.AccountAdapterDetails = test.givenAccountAdapterMetricsDisabled - m.RecordBidValidationSecureMarkupError(adapterLabel, "acct-id") - m.RecordBidValidationSecureMarkupWarn(adapterLabel, "acct-id") + m.RecordBidValidationSecureMarkupError(adapterName, "acct-id") + m.RecordBidValidationSecureMarkupWarn(adapterName, "acct-id") assertCounterVecValue(t, "", "Account Secure Markup Error", m.accountBidResponseSecureMarkupError, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "Adapter Secure Markup Error", m.adapterBidResponseSecureMarkupError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "Adapter Secure Markup Error", m.adapterBidResponseSecureMarkupError, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) assertCounterVecValue(t, "", "Account Secure Markup Warn", m.accountBidResponseSecureMarkupWarn, test.expectedAccountCount, prometheus.Labels{accountLabel: "acct-id", successLabel: successLabel}) - assertCounterVecValue(t, "", "Adapter Secure Markup Warn", m.adapterBidResponseSecureMarkupWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: adapterLabel, successLabel: successLabel}) + assertCounterVecValue(t, "", "Adapter Secure Markup Warn", m.adapterBidResponseSecureMarkupWarn, test.expectedAdapterCount, prometheus.Labels{adapterLabel: lowerCasedAdapterName, successLabel: successLabel}) } } @@ -768,10 +771,11 @@ func TestRecordStoredDataError(t *testing.T) { } func TestAdapterBidReceivedMetric(t *testing.T) { - adapterName := "anyName" + adapterName := openrtb_ext.BidderName("anyName") + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, hasAdm bool) { labels := metrics.AdapterLabels{ - Adapter: openrtb_ext.BidderName(adapterName), + Adapter: adapterName, } bidType := openrtb_ext.BidTypeBanner m.RecordAdapterBidReceived(labels, bidType, hasAdm) @@ -809,13 +813,13 @@ func TestAdapterBidReceivedMetric(t *testing.T) { assertCounterVecValue(t, test.description, "adapterBids[adm]", m.adapterBids, test.expectedAdmCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, markupDeliveryLabel: markupDeliveryAdm, }) assertCounterVecValue(t, test.description, "adapterBids[nurl]", m.adapterBids, test.expectedNurlCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, markupDeliveryLabel: markupDeliveryNurl, }) } @@ -824,6 +828,7 @@ func TestAdapterBidReceivedMetric(t *testing.T) { func TestRecordAdapterPriceMetric(t *testing.T) { m := createMetricsForTesting() adapterName := "anyName" + lowerCasedAdapterName := "anyname" cpm := float64(42) m.RecordAdapterPrice(metrics.AdapterLabels{ @@ -832,12 +837,13 @@ func TestRecordAdapterPriceMetric(t *testing.T) { expectedCount := uint64(1) expectedSum := cpm - result := getHistogramFromHistogramVec(m.adapterPrices, adapterLabel, adapterName) + result := getHistogramFromHistogramVec(m.adapterPrices, adapterLabel, lowerCasedAdapterName) assertHistogram(t, "adapterPrices", result, expectedCount, expectedSum) } func TestAdapterRequestMetrics(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, cookieFlag metrics.CookieFlag, adapterBids metrics.AdapterBid) { labels := metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -937,7 +943,7 @@ func TestAdapterRequestMetrics(t *testing.T) { processMetrics(m.adapterRequests, func(m dto.Metric) { isMetricForAdapter := false for _, label := range m.GetLabel() { - if label.GetName() == adapterLabel && label.GetValue() == adapterName { + if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { isMetricForAdapter = true } } @@ -974,6 +980,7 @@ func TestAdapterRequestMetrics(t *testing.T) { func TestAdapterRequestErrorMetrics(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, adapterErrors map[metrics.AdapterError]struct{}) { labels := metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -1030,7 +1037,7 @@ func TestAdapterRequestErrorMetrics(t *testing.T) { processMetrics(m.adapterErrors, func(m dto.Metric) { isMetricForAdapter := false for _, label := range m.GetLabel() { - if label.GetName() == adapterLabel && label.GetValue() == adapterName { + if label.GetName() == adapterLabel && label.GetValue() == lowerCasedAdapterName { isMetricForAdapter = true } } @@ -1052,6 +1059,7 @@ func TestAdapterRequestErrorMetrics(t *testing.T) { func TestAdapterTimeMetric(t *testing.T) { adapterName := "anyName" + lowerCasedAdapterName := "anyname" performTest := func(m *Metrics, timeInMs float64, adapterErrors map[metrics.AdapterError]struct{}) { m.RecordAdapterTime(metrics.AdapterLabels{ Adapter: openrtb_ext.BidderName(adapterName), @@ -1090,24 +1098,24 @@ func TestAdapterTimeMetric(t *testing.T) { test.testCase(m) - result := getHistogramFromHistogramVec(m.adapterRequestsTimer, adapterLabel, adapterName) + result := getHistogramFromHistogramVec(m.adapterRequestsTimer, adapterLabel, lowerCasedAdapterName) assertHistogram(t, test.description, result, test.expectedCount, test.expectedSum) } } func TestAdapterPanicMetric(t *testing.T) { m := createMetricsForTesting() - adapterName := "anyName" - + adapterName := openrtb_ext.BidderName("anyName") + lowerCasedAdapterName := "anyname" m.RecordAdapterPanic(metrics.AdapterLabels{ - Adapter: openrtb_ext.BidderName(adapterName), + Adapter: adapterName, }) expectedCount := float64(1) assertCounterVecValue(t, "", "adapterPanics", m.adapterPanics, expectedCount, prometheus.Labels{ - adapterLabel: adapterName, + adapterLabel: lowerCasedAdapterName, }) } @@ -1509,6 +1517,8 @@ func TestRecordBidderServerResponseTime(t *testing.T) { } func TestRecordAdapterConnections(t *testing.T) { + adapterName := openrtb_ext.BidderName("Adapter") + lowerCasedAdapterName := "adapter" type testIn struct { adapterName openrtb_ext.BidderName @@ -1531,7 +1541,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[1] Successful, new connection created, was idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: false, connWait: time.Second * 5, }, @@ -1545,7 +1555,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[2] Successful, new connection created, not idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: false, connWait: time.Second * 4, }, @@ -1559,7 +1569,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[3] Successful, was reused, was idle, no connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: true, }, out: testOut{ @@ -1572,7 +1582,7 @@ func TestRecordAdapterConnections(t *testing.T) { { description: "[4] Successful, was reused, not idle, has connection wait", in: testIn{ - adapterName: openrtb_ext.BidderAppnexus, + adapterName: adapterName, connWasReused: true, connWait: time.Second * 5, }, @@ -1602,7 +1612,7 @@ func TestRecordAdapterConnections(t *testing.T) { "adapter_connection_reused", m.adapterReusedConnections, float64(test.out.expectedConnReusedCount), - prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + prometheus.Labels{adapterLabel: lowerCasedAdapterName}) // Assert number of new created connections assertCounterVecValue(t, @@ -1610,10 +1620,10 @@ func TestRecordAdapterConnections(t *testing.T) { "adapter_connection_created", m.adapterCreatedConnections, float64(test.out.expectedConnCreatedCount), - prometheus.Labels{adapterLabel: string(test.in.adapterName)}) + prometheus.Labels{adapterLabel: lowerCasedAdapterName}) // Assert connection wait time - histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, string(test.in.adapterName)) + histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, lowerCasedAdapterName) assert.Equal(t, test.out.expectedConnWaitCount, histogram.GetSampleCount(), assertDesciptions[2]) assert.Equal(t, test.out.expectedConnWaitTime, histogram.GetSampleSum(), assertDesciptions[3]) } @@ -1781,8 +1791,9 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { m := createMetricsForTesting() - - m.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" + m.RecordAdapterGDPRRequestBlocked(adapterName) assertCounterVecValue(t, "Increment adapter GDPR request blocked counter", @@ -1790,7 +1801,7 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { m.adapterGDPRBlockedRequests, 1, prometheus.Labels{ - adapterLabel: string(openrtb_ext.BidderAppnexus), + adapterLabel: lowerCasedAdapterName, }) } @@ -1976,155 +1987,3 @@ func TestRecordModuleMetrics(t *testing.T) { } } } - -func TestRecordAccountGDPRPurposeWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPurposeName string - expectedP1MetricCount float64 - expectedP2MetricCount float64 - expectedP3MetricCount float64 - expectedP4MetricCount float64 - expectedP5MetricCount float64 - expectedP6MetricCount float64 - expectedP7MetricCount float64 - expectedP8MetricCount float64 - expectedP9MetricCount float64 - expectedP10MetricCount float64 - }{ - { - name: "Purpose1MetricIncremented", - givenPurposeName: "purpose1", - expectedP1MetricCount: 1, - }, - { - name: "Purpose2MetricIncremented", - givenPurposeName: "purpose2", - expectedP2MetricCount: 1, - }, - { - name: "Purpose3MetricIncremented", - givenPurposeName: "purpose3", - expectedP3MetricCount: 1, - }, - { - name: "Purpose4MetricIncremented", - givenPurposeName: "purpose4", - expectedP4MetricCount: 1, - }, - { - name: "Purpose5MetricIncremented", - givenPurposeName: "purpose5", - expectedP5MetricCount: 1, - }, - { - name: "Purpose6MetricIncremented", - givenPurposeName: "purpose6", - expectedP6MetricCount: 1, - }, - { - name: "Purpose7MetricIncremented", - givenPurposeName: "purpose7", - expectedP7MetricCount: 1, - }, - { - name: "Purpose8MetricIncremented", - givenPurposeName: "purpose8", - expectedP8MetricCount: 1, - }, - { - name: "Purpose9MetricIncremented", - givenPurposeName: "purpose9", - expectedP9MetricCount: 1, - }, - { - name: "Purpose10MetricIncremented", - givenPurposeName: "purpose10", - expectedP10MetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountGDPRPurposeWarning("acct-id", test.givenPurposeName) - - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose1, test.expectedP1MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose2, test.expectedP2MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose3, test.expectedP3MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose4, test.expectedP4MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose5, test.expectedP5MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose6, test.expectedP6MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose7, test.expectedP7MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose8, test.expectedP8MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose9, test.expectedP9MetricCount) - assertCounterValue(t, "", "Account Deprecation Warnings", m.accountDeprecationWarningsPurpose10, test.expectedP10MetricCount) - }) - } -} - -func TestRecordAccountGDPRChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "GdprChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountGDPRChannelEnabledWarning(test.givenPubID) - - assertCounterValue(t, "", "GDPR Channel Enabled Deprecation Warnings", m.channelEnabledGDPR, test.expectedMetricCount) - }) - } -} - -func TestRecordAccountCCPAChannelEnabledWarningMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "CcpaChannelMetricIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountCCPAChannelEnabledWarning(test.givenPubID) - - assertCounterValue(t, "", "CCPA Channel Enabled Deprecation Warnings", m.channelEnabledCCPA, test.expectedMetricCount) - }) - } -} - -func TestRecordAccountUpgradeStatusMetrics(t *testing.T) { - testCases := []struct { - name string - givenPubID string - expectedMetricCount float64 - }{ - { - name: "AccountDeprecationMeterIncremented", - givenPubID: "acct-id", - expectedMetricCount: 1, - }, - } - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - m := createMetricsForTesting() - m.RecordAccountUpgradeStatus(test.givenPubID) - - assertCounterValue(t, "", "Account Depreciation Summary Meter should be incremented", m.accountDeprecationSummary, test.expectedMetricCount) - }) - - } -} diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 07e4e89c43d..9bf2ec94a08 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -2,13 +2,10 @@ package prometheusmetrics import ( "strconv" - - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "strings" ) -func adaptersAsString() []string { - values := openrtb_ext.CoreBidderNames() +func enumAsString[T ~string](values []T) []string { valuesAsString := make([]string, len(values)) for i, v := range values { valuesAsString[i] = string(v) @@ -16,11 +13,10 @@ func adaptersAsString() []string { return valuesAsString } -func adapterErrorsAsString() []string { - values := metrics.AdapterErrors() +func enumAsLowerCaseString[T ~string](values []T) []string { valuesAsString := make([]string, len(values)) for i, v := range values { - valuesAsString[i] = string(v) + valuesAsString[i] = strings.ToLower(string(v)) } return valuesAsString } @@ -31,111 +27,3 @@ func boolValuesAsString() []string { strconv.FormatBool(false), } } - -func cacheResultsAsString() []string { - values := metrics.CacheResults() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func cookieTypesAsString() []string { - values := metrics.CookieTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func cookieSyncStatusesAsString() []string { - values := metrics.CookieSyncStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func requestStatusesAsString() []string { - values := metrics.RequestStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func syncerRequestStatusesAsString() []string { - values := metrics.SyncerRequestStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func overheadTypesAsString() []string { - overheadTypes := metrics.OverheadTypes() - overheadTypesAsString := make([]string, len(overheadTypes)) - for i, ot := range overheadTypes { - overheadTypesAsString[i] = ot.String() - } - return overheadTypesAsString -} - -func syncerSetStatusesAsString() []string { - values := metrics.SyncerSetUidStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func requestTypesAsString() []string { - values := metrics.RequestTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func setUidStatusesAsString() []string { - values := metrics.SetUidStatuses() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataFetchTypesAsString() []string { - values := metrics.StoredDataFetchTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func storedDataErrorsAsString() []string { - values := metrics.StoredDataErrors() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - -func tcfVersionsAsString() []string { - values := metrics.TCFVersions() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} diff --git a/modules/builder.go b/modules/builder.go index ffb814e6407..e5d04e149af 100644 --- a/modules/builder.go +++ b/modules/builder.go @@ -1,7 +1,7 @@ package modules import ( - prebidOrtb2blocking "github.com/prebid/prebid-server/modules/prebid/ortb2blocking" + prebidOrtb2blocking "github.com/prebid/prebid-server/v2/modules/prebid/ortb2blocking" ) // builders returns mapping between module name and its builder diff --git a/modules/generator/builder.tmpl b/modules/generator/builder.tmpl index f89cc21c87f..b7b78103dbe 100644 --- a/modules/generator/builder.tmpl +++ b/modules/generator/builder.tmpl @@ -3,7 +3,7 @@ package modules {{if .}} import ( {{- range .}} - {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/modules/{{.Vendor}}/{{.Module}}" + {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/v2/modules/{{.Vendor}}/{{.Module}}" {{- end}} ) {{end}} diff --git a/modules/helpers.go b/modules/helpers.go index c7fe9f73f31..10890743691 100644 --- a/modules/helpers.go +++ b/modules/helpers.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) var moduleReplacer = strings.NewReplacer(".", "_", "-", "_") diff --git a/modules/moduledeps/deps.go b/modules/moduledeps/deps.go index 08b18349223..a1fa89173b4 100644 --- a/modules/moduledeps/deps.go +++ b/modules/moduledeps/deps.go @@ -1,9 +1,14 @@ package moduledeps -import "net/http" +import ( + "net/http" + + "github.com/prebid/prebid-server/v2/currency" +) // ModuleDeps provides dependencies that custom modules may need for hooks execution. // Additional dependencies can be added here if modules need something more. type ModuleDeps struct { - HTTPClient *http.Client + HTTPClient *http.Client + RateConvertor *currency.RateConverter } diff --git a/modules/modules.go b/modules/modules.go index ac60ec58082..f3ccd6b1ece 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -5,9 +5,10 @@ import ( "fmt" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) //go:generate go run ./generator/buildergen.go @@ -58,7 +59,7 @@ func (m *builder) Build( id := fmt.Sprintf("%s.%s", vendor, moduleName) if data, ok := cfg[vendor][moduleName]; ok { - if conf, err = json.Marshal(data); err != nil { + if conf, err = jsonutil.Marshal(data); err != nil { return nil, nil, fmt.Errorf(`failed to marshal "%s" module config: %s`, id, err) } diff --git a/modules/modules_test.go b/modules/modules_test.go index 1c70ce4badf..008c1e75c51 100644 --- a/modules/modules_test.go +++ b/modules/modules_test.go @@ -9,10 +9,10 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" "github.com/stretchr/testify/assert" ) @@ -85,7 +85,7 @@ func TestModuleBuilderBuild(t *testing.T) { givenConfig: map[string]map[string]interface{}{vendor: {moduleName: math.Inf(1)}}, expectedHookRepo: nil, expectedModulesStages: nil, - expectedErr: fmt.Errorf(`failed to marshal "%s.%s" module config: json: unsupported value: +Inf`, vendor, moduleName), + expectedErr: fmt.Errorf(`failed to marshal "%s.%s" module config: unsupported value: +Inf`, vendor, moduleName), }, } diff --git a/modules/prebid/ortb2blocking/analytics.go b/modules/prebid/ortb2blocking/analytics.go index 4026b6722b9..1309858c7b6 100644 --- a/modules/prebid/ortb2blocking/analytics.go +++ b/modules/prebid/ortb2blocking/analytics.go @@ -1,8 +1,8 @@ package ortb2blocking import ( - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) const enforceBlockingTag = "enforce_blocking" diff --git a/modules/prebid/ortb2blocking/config.go b/modules/prebid/ortb2blocking/config.go index cefd436b402..4b832cb1977 100644 --- a/modules/prebid/ortb2blocking/config.go +++ b/modules/prebid/ortb2blocking/config.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func newConfig(data json.RawMessage) (config, error) { var cfg config - if err := json.Unmarshal(data, &cfg); err != nil { + if err := jsonutil.UnmarshalValid(data, &cfg); err != nil { return cfg, fmt.Errorf("failed to parse config: %s", err) } return cfg, nil @@ -112,7 +113,7 @@ type Override struct { func (o *Override) UnmarshalJSON(bytes []byte) error { var overrideData interface{} - if err := json.Unmarshal(bytes, &overrideData); err != nil { + if err := jsonutil.UnmarshalValid(bytes, &overrideData); err != nil { return err } diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go index 8f7ce42021c..602e75aec95 100644 --- a/modules/prebid/ortb2blocking/hook_bidderrequest.go +++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go @@ -7,20 +7,20 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) func handleBidderRequestHook( cfg config, payload hookstage.BidderRequestPayload, ) (result hookstage.HookResult[hookstage.BidderRequestPayload], err error) { - if payload.BidRequest == nil { - return result, hookexecution.NewFailure("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return result, hookexecution.NewFailure("payload contains a nil bid request") } - mediaTypes := mediaTypesFrom(payload.BidRequest) + mediaTypes := mediaTypesFrom(payload.Request.BidRequest) changeSet := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} blockingAttributes := blockingAttributes{} @@ -60,7 +60,7 @@ func updateBAdv( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BAdv) > 0 { + if len(payload.Request.BAdv) > 0 { return nil } @@ -87,7 +87,7 @@ func updateBApp( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BApp) > 0 { + if len(payload.Request.BApp) > 0 { return nil } @@ -114,7 +114,7 @@ func updateBCat( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BCat) > 0 { + if len(payload.Request.BCat) > 0 { return nil } @@ -191,7 +191,7 @@ func updateCatTax( attributes *blockingAttributes, changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) { - if payload.BidRequest.CatTax > 0 { + if payload.Request.CatTax > 0 { return } @@ -226,7 +226,7 @@ func mutationForImp( impUpdater impUpdateFunc, ) hookstage.MutationFunc[hookstage.BidderRequestPayload] { return func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - for i, imp := range payload.BidRequest.Imp { + for i, imp := range payload.Request.Imp { if values, ok := valuesByImp[imp.ID]; ok { if len(values) == 0 { continue @@ -236,7 +236,7 @@ func mutationForImp( imp.Banner = &openrtb2.Banner{} } - payload.BidRequest.Imp[i] = impUpdater(imp, values) + payload.Request.Imp[i] = impUpdater(imp, values) } } return payload, nil @@ -310,7 +310,7 @@ func findImpressionOverrides( overrides := map[string][]int{} messages := []string{} - for _, imp := range payload.BidRequest.Imp { + for _, imp := range payload.Request.Imp { // do not add override for attribute if it already exists in request if isAttrPresent(imp) { continue diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index 1c51256211b..615ceda9e04 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -7,9 +7,9 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" ) func handleRawBidderResponseHook( diff --git a/modules/prebid/ortb2blocking/module.go b/modules/prebid/ortb2blocking/module.go index 7386aa62bad..93ca15ff31e 100644 --- a/modules/prebid/ortb2blocking/module.go +++ b/modules/prebid/ortb2blocking/module.go @@ -5,8 +5,8 @@ import ( "encoding/json" "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" ) func Builder(_ json.RawMessage, _ moduledeps.ModuleDeps) (interface{}, error) { diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index b47f9f58a02..fc5cdcc7af6 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -8,11 +8,12 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/hooks/hookanalytics" + "github.com/prebid/prebid-server/v2/hooks/hookexecution" + "github.com/prebid/prebid-server/v2/hooks/hookstage" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -488,7 +489,7 @@ func TestHandleBidderRequestHook(t *testing.T) { bidRequest: &openrtb2.BidRequest{}, expectedBidRequest: &openrtb2.BidRequest{}, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{}, - expectedError: errors.New("failed to parse config: invalid character '.' looking for beginning of value"), + expectedError: errors.New("failed to parse config: expect { or n, but found ."), }, { description: "Expect error if nil BidRequest provided", @@ -497,7 +498,7 @@ func TestHandleBidderRequestHook(t *testing.T) { bidRequest: nil, expectedBidRequest: nil, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{}, - expectedError: hookexecution.NewFailure("empty BidRequest provided"), + expectedError: hookexecution.NewFailure("payload contains a nil bid request"), }, { description: "Expect baadv error if bidders and media_types not defined in config conditions", @@ -566,7 +567,8 @@ func TestHandleBidderRequestHook(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - payload := hookstage.BidderRequestPayload{Bidder: test.bidder, BidRequest: test.bidRequest} + brw := openrtb_ext.RequestWrapper{BidRequest: test.bidRequest} + payload := hookstage.BidderRequestPayload{Bidder: test.bidder, Request: &brw} result, err := Builder(nil, moduledeps.ModuleDeps{}) assert.NoError(t, err, "Failed to build module.") @@ -590,7 +592,8 @@ func TestHandleBidderRequestHook(t *testing.T) { _, err := mut.Apply(payload) assert.NoError(t, err) } - assert.Equal(t, test.expectedBidRequest, payload.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") + + assert.Equal(t, test.expectedBidRequest, payload.Request.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.BidderRequestPayload]{} diff --git a/openrtb_ext/alternatebiddercodes.go b/openrtb_ext/alternatebiddercodes.go index 49291a3e5e8..3dcb6c781fa 100644 --- a/openrtb_ext/alternatebiddercodes.go +++ b/openrtb_ext/alternatebiddercodes.go @@ -1,6 +1,9 @@ package openrtb_ext -import "fmt" +import ( + "fmt" + "strings" +) // ExtAlternateBidderCodes defines list of alternate bidder codes allowed by adatpers. This overrides host level configs. type ExtAlternateBidderCodes struct { @@ -14,7 +17,7 @@ type ExtAdapterAlternateBidderCodes struct { } func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) { - if alternateBidder == "" || bidder == alternateBidder { + if alternateBidder == "" || strings.EqualFold(bidder, alternateBidder) { return true, nil } @@ -26,8 +29,8 @@ func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateB return false, alternateBidderNotDefinedError(bidder, alternateBidder) } - adapterCfg, ok := bidderCodes.Bidders[bidder] - if !ok { + adapterCfg, found := bidderCodes.IsBidderInAlternateBidderCodes(bidder) + if !found { return false, alternateBidderNotDefinedError(bidder, alternateBidder) } @@ -56,3 +59,23 @@ func alternateBidderDisabledError(bidder, alternateBidder string) error { func alternateBidderNotDefinedError(bidder, alternateBidder string) error { return fmt.Errorf("alternateBidderCodes not defined for adapter %q, rejecting bids for %q", bidder, alternateBidder) } + +// IsBidderInAlternateBidderCodes tries to find bidder in the altBidderCodes.Bidders map in a case sensitive +// manner first. If no match is found it'll try it in a case insensitive way in linear time +func (bidderCodes *ExtAlternateBidderCodes) IsBidderInAlternateBidderCodes(bidder string) (ExtAdapterAlternateBidderCodes, bool) { + if len(bidder) > 0 && bidderCodes != nil && len(bidderCodes.Bidders) > 0 { + // try constant time exact match + if adapterCfg, found := bidderCodes.Bidders[bidder]; found { + return adapterCfg, true + } + + // check if we can find with a case insensitive comparison + for bidderName, adapterCfg := range bidderCodes.Bidders { + if strings.EqualFold(bidder, bidderName) { + return adapterCfg, true + } + } + } + + return ExtAdapterAlternateBidderCodes{}, false +} diff --git a/openrtb_ext/alternatebiddercodes_test.go b/openrtb_ext/alternatebiddercodes_test.go index 438aebad559..f9229b13d2a 100644 --- a/openrtb_ext/alternatebiddercodes_test.go +++ b/openrtb_ext/alternatebiddercodes_test.go @@ -35,6 +35,14 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "alternateBidder and bidder are the same under Unicode case-folding (default non-extra bid case with seat's alternateBidder explicitly set)", + args: args{ + bidder: "pubmatic", + alternateBidder: "pubmatic", + }, + wantIsValid: true, + }, { name: "account.alternatebiddercodes config not defined (default, reject bid)", args: args{ @@ -98,6 +106,20 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "bidder is different in casing than the entry in account.alternatebiddercodes but they match because our case insensitive comparison", + args: args{ + bidder: "PUBmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true}, + }, + }, + wantIsValid: true, + }, { name: "allowedBidderCodes is *", args: args{ @@ -178,3 +200,129 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }) } } + +func TestIsBidderInAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + bidderCodes *ExtAlternateBidderCodes + } + type testOutput struct { + adapterCfg ExtAdapterAlternateBidderCodes + found bool + } + testCases := []struct { + desc string + in testInput + expected testOutput + }{ + { + desc: "empty bidder", + in: testInput{ + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes", + in: testInput{ + bidder: "appnexus", + bidderCodes: nil, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: nil, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "bidder arg identical to entry in Bidders map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "appnexus": { + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg matches an entry in Bidders map with case insensitive comparisson", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + AllowedBidderCodes: []string{"adnxsCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg doesn't match any entry in map", + in: testInput{ + bidder: "unknown", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + adapterCfg, found := tc.in.bidderCodes.IsBidderInAlternateBidderCodes(tc.in.bidder) + assert.Equal(t, tc.expected.adapterCfg, adapterCfg) + assert.Equal(t, tc.expected.found, found) + }) + } +} diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 20bbf0b3734..2e190389212 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -50,6 +50,7 @@ type ExtBidPrebidCacheBids struct { // ExtBidPrebidMeta defines the contract for bidresponse.seatbid.bid[i].ext.prebid.meta type ExtBidPrebidMeta struct { + AdapterCode string `json:"adaptercode,omitempty"` AdvertiserDomains []string `json:"advertiserDomains,omitempty"` AdvertiserID int `json:"advertiserId,omitempty"` AdvertiserName string `json:"advertiserName,omitempty"` @@ -57,14 +58,17 @@ type ExtBidPrebidMeta struct { AgencyName string `json:"agencyName,omitempty"` BrandID int `json:"brandId,omitempty"` BrandName string `json:"brandName,omitempty"` - DemandSource string `json:"demandSource,omitempty"` DChain json.RawMessage `json:"dchain,omitempty"` + DemandSource string `json:"demandSource,omitempty"` MediaType string `json:"mediaType,omitempty"` NetworkID int `json:"networkId,omitempty"` NetworkName string `json:"networkName,omitempty"` PrimaryCategoryID string `json:"primaryCatId,omitempty"` + RendererName string `json:"rendererName,omitempty"` + RendererVersion string `json:"rendererVersion,omitempty"` + RendererData json.RawMessage `json:"rendererData,omitempty"` + RendererUrl string `json:"rendererUrl,omitempty"` SecondaryCategoryIDs []string `json:"secondaryCatIds,omitempty"` - AdapterCode string `json:"adaptercode,omitempty"` } // ExtBidPrebidVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.video diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index f95d4a36540..98404a205b1 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -2,7 +2,11 @@ package openrtb_ext import ( "encoding/json" + "errors" + "os" + "strings" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/xeipuuv/gojsonschema" @@ -125,3 +129,156 @@ func TestIsBidderNameReserved(t *testing.T) { assert.Equal(t, test.expected, result, test.bidder) } } + +func TestSetAliasBidderName(t *testing.T) { + parentBidder := BidderName("pBidder") + existingCoreBidderNames := coreBidderNames + + testCases := []struct { + aliasBidderName string + err error + }{ + {"aBidder", nil}, + {"all", errors.New("alias all is a reserved bidder name and cannot be used")}, + } + + for _, test := range testCases { + err := SetAliasBidderName(test.aliasBidderName, parentBidder) + if err != nil { + assert.Equal(t, test.err, err) + } else { + assert.Contains(t, CoreBidderNames(), BidderName(test.aliasBidderName)) + assert.Contains(t, aliasBidderToParent, BidderName(test.aliasBidderName)) + assert.Contains(t, bidderNameLookup, strings.ToLower(test.aliasBidderName)) + } + } + + //reset package variables to not interfere with other test cases. Example - TestBidderParamSchemas + coreBidderNames = existingCoreBidderNames + aliasBidderToParent = map[BidderName]BidderName{} +} + +type mockParamsHelper struct { + fs fstest.MapFS + absFilePath string + absPathErr error + schemaLoaderErr error + readFileErr error +} + +func (m *mockParamsHelper) readDir(name string) ([]os.DirEntry, error) { + return m.fs.ReadDir(name) +} + +func (m *mockParamsHelper) readFile(name string) ([]byte, error) { + if m.readFileErr != nil { + return nil, m.readFileErr + } + return m.fs.ReadFile(name) +} + +func (m *mockParamsHelper) newReferenceLoader(source string) gojsonschema.JSONLoader { + return nil +} + +func (m *mockParamsHelper) newSchema(l gojsonschema.JSONLoader) (*gojsonschema.Schema, error) { + return nil, m.schemaLoaderErr +} + +func (m *mockParamsHelper) abs(path string) (string, error) { + return m.absFilePath, m.absPathErr +} + +func TestNewBidderParamsValidator(t *testing.T) { + testCases := []struct { + description string + paramsValidator mockParamsHelper + dir string + expectedErr error + }{ + { + description: "Valid case", + paramsValidator: mockParamsHelper{ + fs: fstest.MapFS{ + "test/appnexus.json": { + Data: []byte("{}"), + }, + }, + }, + dir: "test", + }, + { + description: "failed to read directory", + paramsValidator: mockParamsHelper{}, + dir: "t", + expectedErr: errors.New("Failed to read JSON schemas from directory t. open t: file does not exist"), + }, + { + description: "file name does not match the bidder name", + paramsValidator: mockParamsHelper{ + fs: fstest.MapFS{ + "test/anyBidder.json": { + Data: []byte("{}"), + }, + }, + }, + dir: "test", + expectedErr: errors.New("File test/anyBidder.json does not match a valid BidderName."), + }, + { + description: "abs file path error", + paramsValidator: mockParamsHelper{ + fs: fstest.MapFS{ + "test/appnexus.json": { + Data: []byte("{}"), + }, + }, + absFilePath: "test/app.json", + absPathErr: errors.New("any abs error"), + }, + dir: "test", + expectedErr: errors.New("Failed to get an absolute representation of the path: test/app.json, any abs error"), + }, + { + description: "schema loader error", + paramsValidator: mockParamsHelper{ + fs: fstest.MapFS{ + "test/appnexus.json": { + Data: []byte("{}"), + }, + }, + schemaLoaderErr: errors.New("any schema loader error"), + }, + dir: "test", + expectedErr: errors.New("Failed to load json schema at : any schema loader error"), + }, + { + description: "read file error", + paramsValidator: mockParamsHelper{ + fs: fstest.MapFS{ + "test/appnexus.json": { + Data: []byte("{}"), + }, + }, + readFileErr: errors.New("any read file error"), + }, + dir: "test", + expectedErr: errors.New("Failed to read file test/appnexus.json: any read file error"), + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + aliasBidderToParent = map[BidderName]BidderName{"rubicon": "appnexus"} + paramsValidator = &test.paramsValidator + bidderValidator, err := NewBidderParamsValidator(test.dir) + if test.expectedErr == nil { + assert.NoError(t, err) + assert.Contains(t, bidderValidator.Schema("appnexus"), "{}") + assert.Contains(t, bidderValidator.Schema("rubicon"), "{}") + } else { + assert.Equal(t, err, test.expectedErr) + } + }) + } +} diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 530f260c761..66de818311d 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -50,7 +50,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. var bidders []string for _, bidder := range CoreBidderNames() { - if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld { + if bidder != BidderSilverPush && bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != "freewheel-ssp" && bidder != "yahooAdvertising" { bidders = append(bidders, string(bidder)) } } diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go index ea21468e737..a72ba3a48ad 100644 --- a/openrtb_ext/convert_down_test.go +++ b/openrtb_ext/convert_down_test.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) @@ -14,7 +15,7 @@ func TestConvertDownTo25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "2.6-to-2.5", @@ -87,12 +88,12 @@ func TestConvertDownTo25(t *testing.T) { }, }, { - name: "malformed-shhain", + name: "malformed-schain", givenRequest: openrtb2.BidRequest{ ID: "anyID", Source: &openrtb2.Source{SChain: &openrtb2.SupplyChain{Complete: 1, Nodes: []openrtb2.SupplyChainNode{}, Ver: "2"}, Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-gdpr", @@ -100,7 +101,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(1), Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-consent", @@ -108,7 +109,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-usprivacy", @@ -116,7 +117,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Regs: &openrtb2.Regs{USPrivacy: "3", Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-eid", @@ -124,7 +125,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", User: &openrtb2.User{EIDs: []openrtb2.EID{{Source: "42"}}, Ext: json.RawMessage(`malformed`)}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, { name: "malformed-imp", @@ -132,7 +133,7 @@ func TestConvertDownTo25(t *testing.T) { ID: "anyID", Imp: []openrtb2.Imp{{Rwdd: 1, Ext: json.RawMessage(`malformed`)}}, }, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -141,8 +142,8 @@ func TestConvertDownTo25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := ConvertDownTo25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -162,7 +163,7 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-source", @@ -185,9 +186,9 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: schain1Json}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{SChain: schain1, Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -196,8 +197,8 @@ func TestMoveSupplyChainFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveSupplyChainFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -211,7 +212,7 @@ func TestMoveGDPRFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-regs", @@ -234,9 +235,9 @@ func TestMoveGDPRFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{GDPR: openrtb2.Int8Ptr(0), Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -245,8 +246,8 @@ func TestMoveGDPRFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveGDPRFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -260,7 +261,7 @@ func TestMoveConsentFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-user", @@ -283,9 +284,9 @@ func TestMoveConsentFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1"}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Consent: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -294,8 +295,8 @@ func TestMoveConsentFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveConsentFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -309,7 +310,7 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-regs", @@ -332,9 +333,9 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1"}`)}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{USPrivacy: "1", Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -343,8 +344,8 @@ func TestMoveUSPrivacyFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveUSPrivacyFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -364,7 +365,7 @@ func TestMoveEIDFrom26To25(t *testing.T) { name string givenRequest openrtb2.BidRequest expectedRequest openrtb2.BidRequest - expectedErr string + expectedErrType error }{ { name: "notpresent-user", @@ -392,9 +393,9 @@ func TestMoveEIDFrom26To25(t *testing.T) { expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: eid1Json}}, }, { - name: "malformed", - givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "malformed", + givenRequest: openrtb2.BidRequest{User: &openrtb2.User{EIDs: eid1, Ext: json.RawMessage(`malformed`)}}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -403,8 +404,8 @@ func TestMoveEIDFrom26To25(t *testing.T) { w := &RequestWrapper{BidRequest: &test.givenRequest} err := moveEIDFrom26To25(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildRequest(), "error") assert.Equal(t, test.expectedRequest, *w.BidRequest, "result") @@ -415,10 +416,10 @@ func TestMoveEIDFrom26To25(t *testing.T) { func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { testCases := []struct { - name string - givenImp openrtb2.Imp - expectedImp openrtb2.Imp - expectedErr string + name string + givenImp openrtb2.Imp + expectedImp openrtb2.Imp + expectedErrType error }{ { name: "notpresent-prebid", @@ -436,9 +437,9 @@ func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`)}, }, { - name: "Malformed", - givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`malformed`)}, - expectedErr: "invalid character 'm' looking for beginning of value", + name: "Malformed", + givenImp: openrtb2.Imp{Rwdd: 1, Ext: json.RawMessage(`malformed`)}, + expectedErrType: &errortypes.FailedToUnmarshal{}, }, } @@ -447,8 +448,8 @@ func TestMoveRewardedFrom26ToPrebidExt(t *testing.T) { w := &ImpWrapper{Imp: &test.givenImp} err := moveRewardedFrom26ToPrebidExt(w) - if len(test.expectedErr) > 0 { - assert.EqualError(t, err, test.expectedErr, "error") + if test.expectedErrType != nil { + assert.IsType(t, test.expectedErrType, err) } else { assert.NoError(t, w.RebuildImp(), "error") assert.Equal(t, test.expectedImp, *w.Imp, "result") diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go index d3ba034d07e..3cafe8c1612 100644 --- a/openrtb_ext/convert_up_test.go +++ b/openrtb_ext/convert_up_test.go @@ -20,7 +20,7 @@ func TestConvertUpTo26(t *testing.T) { givenRequest: openrtb2.BidRequest{ Ext: json.RawMessage(`malformed`), }, - expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.ext is invalid: expect { or n, but found m", }, { description: "2.4 -> 2.6", @@ -120,27 +120,27 @@ func TestConvertUpEnsureExt(t *testing.T) { { description: "Ext", givenRequest: openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, - expectedErr: "req.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.ext is invalid: expect { or n, but found m", }, { description: "Source.Ext", givenRequest: openrtb2.BidRequest{Source: &openrtb2.Source{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.source.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.source.ext is invalid: expect { or n, but found m", }, { description: "Regs.Ext", givenRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.regs.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.regs.ext is invalid: expect { or n, but found m", }, { description: "User.Ext", givenRequest: openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage("malformed")}}, - expectedErr: "req.user.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "req.user.ext is invalid: expect { or n, but found m", }, { description: "Imp.Ext", givenRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: json.RawMessage("malformed")}}}, - expectedErr: "imp[0].imp.ext is invalid: invalid character 'm' looking for beginning of value", + expectedErr: "imp[0].imp.ext is invalid: expect { or n, but found m", }, } diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index 45285d21663..bd8d681dbb4 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -1,9 +1,8 @@ package openrtb_ext import ( - "encoding/json" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // DealTier defines the configuration of a deal tier. @@ -34,12 +33,14 @@ func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { } `json:"bidder"` } `json:"prebid"` } - if err := json.Unmarshal(imp.Ext, &impPrebidExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &impPrebidExt); err != nil { return nil, err } for bidder, param := range impPrebidExt.Prebid.Bidders { if param.DealTier != nil { - dealTiers[BidderName(bidder)] = *param.DealTier + if bidderNormalized, bidderFound := NormalizeBidderName(bidder); bidderFound { + dealTiers[bidderNormalized] = *param.DealTier + } } } diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 0046b788ece..6b74f7a1ff6 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -5,89 +5,112 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/errortypes" "github.com/stretchr/testify/assert" ) func TestReadDealTiersFromImp(t *testing.T) { testCases := []struct { - description string - impExt json.RawMessage - expectedResult DealTierBidderMap - expectedError string + description string + impExt json.RawMessage + expectedResult DealTierBidderMap + expectedErrorType error }{ { - description: "Nil", + description: "nil", impExt: nil, expectedResult: DealTierBidderMap{}, }, { - description: "None", + description: "none", impExt: json.RawMessage(``), expectedResult: DealTierBidderMap{}, }, { - description: "Empty Object", + description: "empty_object", impExt: json.RawMessage(`{}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext - no prebid but with other params", + description: "imp.ext_no_prebid_but_with_other_params", impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}, "tid": "1234"}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - nil", + description: "imp.ext.prebid_nil", impExt: json.RawMessage(`{"prebid": null}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - empty", + description: "imp.ext.prebid_empty", impExt: json.RawMessage(`{"prebid": {}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - no bidder but with other params", + description: "imp.ext.prebid_no bidder but with other params", impExt: json.RawMessage(`{"prebid": {"supportdeals": true}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid.bidder - one", + description: "imp.ext.prebid.bidder_one", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, }, { - description: "imp.ext.prebid.bidder - one with other params", + description: "imp.ext.prebid.bidder_one_but_not_found_in_the_adapter_bidder_list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"unknown": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{}, + }, + { + description: "imp.ext.prebid.bidder_one_but_case_is_different_from_the_adapter_bidder_list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"APpNExUS": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, + }, + { + description: "imp.ext.prebid.bidder_one_with_other_params", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}, "supportdeals": true}, "tid": "1234"}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, }, { - description: "imp.ext.prebid.bidder - multiple", + description: "imp.ext.prebid.bidder_multiple", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "appnexusPrefix"}, "placementId": 12345}, "rubicon": {"dealTier": {"minDealTier": 8, "prefix": "rubiconPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "appnexusPrefix", MinDealTier: 5}, BidderRubicon: {Prefix: "rubiconPrefix", MinDealTier: 8}}, }, { - description: "imp.ext.prebid.bidder - one without deal tier", + description: "imp.ext.prebid.bidder_one_without_deal_tier", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"placementId": 12345}}}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid.bidder - error", - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`), - expectedError: "json: cannot unmarshal string into Go struct field .prebid.bidder.dealTier of type openrtb_ext.DealTier", + description: "imp.ext.prebid.bidder_error", + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`), + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, } for _, test := range testCases { - imp := openrtb2.Imp{Ext: test.impExt} + t.Run(test.description, func(t *testing.T) { - result, err := ReadDealTiersFromImp(imp) + imp := openrtb2.Imp{Ext: test.impExt} + result, err := ReadDealTiersFromImp(imp) - assert.Equal(t, test.expectedResult, result, test.description+":result") + assert.Equal(t, test.expectedResult, result) - if len(test.expectedError) == 0 { - assert.NoError(t, err, test.description+":error") - } else { - assert.EqualError(t, err, test.expectedError, test.description+":error") - } + if test.expectedErrorType != nil { + assert.IsType(t, test.expectedErrorType, err) + } else { + assert.NoError(t, err) + } + }) } + + t.Run("imp.ext.prebid.bidder_dedupe", func(t *testing.T) { + impExt := json.RawMessage(`{"prebid": {"bidder": {"APPNEXUS": {"dealTier": {"minDealTier": 100}},"APpNExUS": {"dealTier": {"minDealTier": 5}}}}}`) + imp := openrtb2.Imp{Ext: impExt} + result, err := ReadDealTiersFromImp(imp) + + assert.Len(t, result, 1) + assert.NotNil(t, result["appnexus"]) + assert.NoError(t, err) + }) } diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index 8c5b36733b9..0888d06160f 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) // PrebidExtKey represents the prebid extension key used in requests diff --git a/openrtb_ext/device_test.go b/openrtb_ext/device_test.go index 1a3dbe8e2f4..f40e9650061 100644 --- a/openrtb_ext/device_test.go +++ b/openrtb_ext/device_test.go @@ -4,30 +4,31 @@ import ( "encoding/json" "testing" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestInvalidDeviceExt(t *testing.T) { var s ExtDevice - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":105}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":true,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":null,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":"75","minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":105}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":true,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":null,"minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":"75","minheightperc":0}}}`), &s), "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":-5}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":false}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") - assert.EqualError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":"75"}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":-5}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":false}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":85,"minheightperc":"75"}}}`), &s), "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100") } func TestValidDeviceExt(t *testing.T) { var s ExtDevice - assert.NoError(t, json.Unmarshal([]byte(`{"prebid":{}}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{}}`), &s)) assert.Nil(t, s.Prebid.Interstitial) - assert.NoError(t, json.Unmarshal([]byte(`{}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{}`), &s)) assert.Nil(t, s.Prebid.Interstitial) - assert.NoError(t, json.Unmarshal([]byte(`{"prebid":{"interstitial":{"minwidthperc":75,"minheightperc":60}}}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"prebid":{"interstitial":{"minwidthperc":75,"minheightperc":60}}}`), &s)) assert.EqualValues(t, 75, s.Prebid.Interstitial.MinWidthPerc) assert.EqualValues(t, 60, s.Prebid.Interstitial.MinHeightPerc) } diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go index 3553946cc2e..0e773c65899 100644 --- a/openrtb_ext/floors.go +++ b/openrtb_ext/floors.go @@ -1,5 +1,11 @@ package openrtb_ext +import ( + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" +) + // Defines strings for FetchStatus const ( FetchSuccess = "success" @@ -145,3 +151,59 @@ type ExtImp struct { type ImpExtPrebid struct { Floors Price `json:"floors,omitempty"` } + +func (pf *PriceFloorRules) DeepCopy() *PriceFloorRules { + if pf == nil { + return nil + } + + newRules := *pf + newRules.Enabled = ptrutil.Clone(pf.Enabled) + newRules.Skipped = ptrutil.Clone(pf.Skipped) + newRules.Location = ptrutil.Clone(pf.Location) + newRules.Data = pf.Data.DeepCopy() + newRules.Enforcement = pf.Enforcement.DeepCopy() + + return &newRules +} + +func (data *PriceFloorData) DeepCopy() *PriceFloorData { + if data == nil { + return nil + } + + newData := *data + newModelGroups := make([]PriceFloorModelGroup, len(data.ModelGroups)) + + for i := range data.ModelGroups { + var eachGroup PriceFloorModelGroup + eachGroup.Currency = data.ModelGroups[i].Currency + eachGroup.ModelWeight = ptrutil.Clone(data.ModelGroups[i].ModelWeight) + eachGroup.ModelVersion = data.ModelGroups[i].ModelVersion + eachGroup.SkipRate = data.ModelGroups[i].SkipRate + eachGroup.Values = maputil.Clone(data.ModelGroups[i].Values) + eachGroup.Default = data.ModelGroups[i].Default + eachGroup.Schema = PriceFloorSchema{ + Fields: sliceutil.Clone(data.ModelGroups[i].Schema.Fields), + Delimiter: data.ModelGroups[i].Schema.Delimiter, + } + newModelGroups[i] = eachGroup + } + newData.ModelGroups = newModelGroups + + return &newData +} + +func (enforcement *PriceFloorEnforcement) DeepCopy() *PriceFloorEnforcement { + if enforcement == nil { + return nil + } + + newEnforcement := *enforcement + newEnforcement.EnforceJS = ptrutil.Clone(enforcement.EnforceJS) + newEnforcement.EnforcePBS = ptrutil.Clone(enforcement.EnforcePBS) + newEnforcement.FloorDeals = ptrutil.Clone(enforcement.FloorDeals) + newEnforcement.BidAdjustment = ptrutil.Clone(enforcement.BidAdjustment) + + return &newEnforcement +} diff --git a/openrtb_ext/floors_test.go b/openrtb_ext/floors_test.go index 20247736768..d6c35b9f7ef 100644 --- a/openrtb_ext/floors_test.go +++ b/openrtb_ext/floors_test.go @@ -1,8 +1,10 @@ package openrtb_ext import ( + "reflect" "testing" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -218,3 +220,263 @@ func TestPriceFloorRulesGetEnabled(t *testing.T) { }) } } + +func TestPriceFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + }{ + { + name: "DeepCopy does not share same reference", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "https://test/floors", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(1), + SkipRate: 0, + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + got := pf.DeepCopy() + if got == pf { + t.Errorf("Rules reference are same") + } + if got.Data == pf.Data { + t.Errorf("Floor data reference is same") + } + }) + } +} + +func TestFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + want *PriceFloorRules + }{ + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: "2", + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: "2", + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + if got := pf.DeepCopy(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFloorRuleDeepCopyNil(t *testing.T) { + var priceFloorRule *PriceFloorRules + got := priceFloorRule.DeepCopy() + + if got != nil { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, nil) + } +} diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go index b1e41e806b6..49833e90f1d 100644 --- a/openrtb_ext/imp_adnuntius.go +++ b/openrtb_ext/imp_adnuntius.go @@ -3,5 +3,7 @@ package openrtb_ext type ImpExtAdnunitus struct { Auid string `json:"auId"` Network string `json:"network"` - NoCookies bool `json:noCookies` + NoCookies bool `json:"noCookies"` + MaxDeals int `json:"maxDeals"` + BidType string `json:"bidType,omitempty"` } diff --git a/openrtb_ext/imp_adocean.go b/openrtb_ext/imp_adocean.go index e690e929778..1dd64d284e2 100644 --- a/openrtb_ext/imp_adocean.go +++ b/openrtb_ext/imp_adocean.go @@ -1,7 +1,7 @@ package openrtb_ext type ExtImpAdOcean struct { - EmitterDomain string `json:"emiter"` + EmitterPrefix string `json:"emitterPrefix"` MasterID string `json:"masterId"` SlaveID string `json:"slaveId"` } diff --git a/openrtb_ext/imp_adquery.go b/openrtb_ext/imp_adquery.go new file mode 100644 index 00000000000..73477e784b1 --- /dev/null +++ b/openrtb_ext/imp_adquery.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtAdQuery struct { + PlacementID string `json:"placementId"` + Type string `json:"type"` +} diff --git a/openrtb_ext/imp_alkimi.go b/openrtb_ext/imp_alkimi.go new file mode 100644 index 00000000000..640528f212f --- /dev/null +++ b/openrtb_ext/imp_alkimi.go @@ -0,0 +1,9 @@ +package openrtb_ext + +type ExtImpAlkimi struct { + Token string `json:"token"` + BidFloor float64 `json:"bidFloor"` + Instl int8 `json:"instl"` + Exp int64 `json:"exp"` + AdUnitCode string `json:"adUnitCode"` +} diff --git a/openrtb_ext/imp_applogy.go b/openrtb_ext/imp_applogy.go deleted file mode 100644 index 45774a05afb..00000000000 --- a/openrtb_ext/imp_applogy.go +++ /dev/null @@ -1,5 +0,0 @@ -package openrtb_ext - -type ExtImpApplogy struct { - Token string `json:"token"` -} diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index d9549e74750..db949f661fd 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // ExtImpAppnexus defines the contract for bidrequest.imp[i].ext.prebid.bidder.appnexus @@ -45,7 +45,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { switch b[0] { case '{': var results map[string][]string - if err := json.Unmarshal(b, &results); err != nil { + if err := jsonutil.UnmarshalValid(b, &results); err != nil { return err } @@ -64,7 +64,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { } case '[': var results []extImpAppnexusKeyVal - if err := json.Unmarshal(b, &results); err != nil { + if err := jsonutil.UnmarshalValid(b, &results); err != nil { return err } var kvs strings.Builder @@ -82,7 +82,7 @@ func (ks *ExtImpAppnexusKeywords) UnmarshalJSON(b []byte) error { } case '"': var keywords string - if err := json.Unmarshal(b, &keywords); err != nil { + if err := jsonutil.UnmarshalValid(b, &keywords); err != nil { return err } *ks = ExtImpAppnexusKeywords(keywords) diff --git a/openrtb_ext/imp_appnexus_test.go b/openrtb_ext/imp_appnexus_test.go index cbd6779d5da..9d65f3be3ad 100644 --- a/openrtb_ext/imp_appnexus_test.go +++ b/openrtb_ext/imp_appnexus_test.go @@ -1,9 +1,9 @@ package openrtb_ext import ( - "encoding/json" "testing" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -31,7 +31,7 @@ func TestKeywordsUnmarshalJSON(t *testing.T) { for _, test := range validTestCases { var keywords keywords - assert.NoError(t, json.Unmarshal(test.input, &keywords), test.desc) + assert.NoError(t, jsonutil.UnmarshalValid(test.input, &keywords), test.desc) assert.Equal(t, test.expected, keywords.Keywords.String()) } @@ -42,6 +42,6 @@ func TestKeywordsUnmarshalJSON(t *testing.T) { for _, test := range invalidTestCases { var keywords keywords - assert.Error(t, json.Unmarshal(test.input, &keywords), test.desc) + assert.Error(t, jsonutil.UnmarshalValid(test.input, &keywords), test.desc) } } diff --git a/openrtb_ext/imp_bematterfull.go b/openrtb_ext/imp_bematterfull.go new file mode 100644 index 00000000000..bc3ca451a61 --- /dev/null +++ b/openrtb_ext/imp_bematterfull.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtBematterfull struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/openrtb_ext/imp_brightroll.go b/openrtb_ext/imp_brightroll.go deleted file mode 100644 index bd42d4aae5d..00000000000 --- a/openrtb_ext/imp_brightroll.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpBrightroll defines the contract for bidrequest.imp[i].ext.prebid.bidder.brightroll -type ExtImpBrightroll struct { - Publisher string `json:"publisher"` -} diff --git a/openrtb_ext/imp_emx_digital.go b/openrtb_ext/imp_cadent_aperture_mx.go similarity index 72% rename from openrtb_ext/imp_emx_digital.go rename to openrtb_ext/imp_cadent_aperture_mx.go index 5c5956b1fc1..fc4280ff491 100644 --- a/openrtb_ext/imp_emx_digital.go +++ b/openrtb_ext/imp_cadent_aperture_mx.go @@ -1,6 +1,6 @@ package openrtb_ext -type ExtImpEmxDigital struct { +type ExtImpCadentApertureMX struct { TagID string `json:"tagid"` BidFloor string `json:"bidfloor,omitempty"` } diff --git a/openrtb_ext/imp_consumable.go b/openrtb_ext/imp_consumable.go index fe916b09972..8158b41d2cc 100644 --- a/openrtb_ext/imp_consumable.go +++ b/openrtb_ext/imp_consumable.go @@ -6,5 +6,6 @@ type ExtImpConsumable struct { SiteId int `json:"siteId,omitempty"` UnitId int `json:"unitId,omitempty"` /* UnitName gets used as a classname and in the URL when building the ad markup */ - UnitName string `json:"unitName,omitempty"` + UnitName string `json:"unitName,omitempty"` + PlacementId string `json:"placementid,omitempty"` } diff --git a/openrtb_ext/imp_datablocks.go b/openrtb_ext/imp_datablocks.go index c8659d3ff11..dc3bae3f3fe 100644 --- a/openrtb_ext/imp_datablocks.go +++ b/openrtb_ext/imp_datablocks.go @@ -2,6 +2,5 @@ package openrtb_ext // ExtImpDatablocks defines the contract for bidrequest.imp[i].ext.prebid.bidder.datablocks type ExtImpDatablocks struct { - SourceId int `json:"sourceId"` - Host string `json:"host"` + SourceId int `json:"sourceId"` } diff --git a/openrtb_ext/imp_dxkulture.go b/openrtb_ext/imp_dxkulture.go new file mode 100644 index 00000000000..4b507c55248 --- /dev/null +++ b/openrtb_ext/imp_dxkulture.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpDXKulture defines the contract for bidrequest.imp[i].ext.prebid.bidder.dxkulture +type ExtImpDXKulture struct { + PublisherId string `json:"publisherId"` + PlacementId string `json:"placementId"` +} diff --git a/openrtb_ext/imp_edge226.go b/openrtb_ext/imp_edge226.go new file mode 100644 index 00000000000..44f3490ee42 --- /dev/null +++ b/openrtb_ext/imp_edge226.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtEdge226 struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_engagebdr.go b/openrtb_ext/imp_engagebdr.go deleted file mode 100644 index db500111a78..00000000000 --- a/openrtb_ext/imp_engagebdr.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpEngageBDR defines the contract for bidrequest.imp[i].ext.prebid.bidder.engagebdr -type ExtImpEngageBDR struct { - Sspid string `json:"sspid"` -} diff --git a/openrtb_ext/imp_flipp.go b/openrtb_ext/imp_flipp.go index 029e2882652..56874c024c6 100644 --- a/openrtb_ext/imp_flipp.go +++ b/openrtb_ext/imp_flipp.go @@ -6,7 +6,6 @@ type ImpExtFlipp struct { SiteID int64 `json:"siteId"` ZoneIds []int64 `json:"zoneIds,omitempty"` UserKey string `json:"userKey,omitempty"` - IP string `json:"ip,omitempty"` Options ImpExtFlippOptions `json:"options,omitempty"` } diff --git a/openrtb_ext/imp_freewheelssp.go b/openrtb_ext/imp_freewheelssp.go index 11c22ff8154..3d015d96722 100644 --- a/openrtb_ext/imp_freewheelssp.go +++ b/openrtb_ext/imp_freewheelssp.go @@ -1,5 +1,9 @@ package openrtb_ext +import ( + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + type ImpExtFreewheelSSP struct { - ZoneId int `json:"zoneId"` + ZoneId jsonutil.StringInt `json:"zoneId"` } diff --git a/openrtb_ext/imp_gothamads.go b/openrtb_ext/imp_gothamads.go new file mode 100644 index 00000000000..fb945fbba67 --- /dev/null +++ b/openrtb_ext/imp_gothamads.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtGothamAds struct { + AccountID string `json:"accountId"` +} diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 96a1308d663..f54234fa394 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -3,10 +3,11 @@ package openrtb_ext // ExtImpGumGum defines the contract for bidrequest.imp[i].ext.prebid.bidder.gumgum // Either Zone or PubId must be present, others are optional parameters type ExtImpGumGum struct { - Zone string `json:"zone,omitempty"` - PubID float64 `json:"pubId,omitempty"` - IrisID string `json:"irisid,omitempty"` - Slot float64 `json:"slot,omitempty"` + Zone string `json:"zone,omitempty"` + PubID float64 `json:"pubId,omitempty"` + IrisID string `json:"irisid,omitempty"` + Slot float64 `json:"slot,omitempty"` + Product string `json:"product,omitempty"` } // ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.bidder.gumgum.video diff --git a/openrtb_ext/imp_iqx.go b/openrtb_ext/imp_iqx.go new file mode 100644 index 00000000000..0b0358b67e1 --- /dev/null +++ b/openrtb_ext/imp_iqx.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtIQX struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/openrtb_ext/imp_ix.go b/openrtb_ext/imp_ix.go index 9f977fb0dcd..40c3f51867f 100644 --- a/openrtb_ext/imp_ix.go +++ b/openrtb_ext/imp_ix.go @@ -4,4 +4,5 @@ package openrtb_ext type ExtImpIx struct { SiteId string `json:"siteId"` Size []int `json:"size"` + Sid string `json:"sid"` } diff --git a/openrtb_ext/imp_kubient.go b/openrtb_ext/imp_kubient.go deleted file mode 100644 index 59dd3d2aaab..00000000000 --- a/openrtb_ext/imp_kubient.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -// ExtImpKubient defines the contract for bidrequest.imp[i].ext.prebid.bidder.kubient -type ExtImpKubient struct { - ZoneID string `json:"zoneid"` -} diff --git a/openrtb_ext/imp_lemmadigital.go b/openrtb_ext/imp_lemmadigital.go new file mode 100644 index 00000000000..c691dd173d9 --- /dev/null +++ b/openrtb_ext/imp_lemmadigital.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtLemmaDigital struct { + PublisherId int `json:"pid"` + AdId int `json:"aid"` +} diff --git a/openrtb_ext/imp_liftoff.go b/openrtb_ext/imp_liftoff.go new file mode 100644 index 00000000000..d8a93d8906a --- /dev/null +++ b/openrtb_ext/imp_liftoff.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ImpExtLiftoff struct { + BidToken string `json:"bid_token"` + PubAppStoreID string `json:"app_store_id"` + PlacementRefID string `json:"placement_reference_id"` +} diff --git a/openrtb_ext/imp_lmkiviads.go b/openrtb_ext/imp_lmkiviads.go new file mode 100644 index 00000000000..8638749b1b3 --- /dev/null +++ b/openrtb_ext/imp_lmkiviads.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtLmKiviads struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/openrtb_ext/imp_mabidder.go b/openrtb_ext/imp_mabidder.go new file mode 100644 index 00000000000..796a4877215 --- /dev/null +++ b/openrtb_ext/imp_mabidder.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtMabidder struct { + Ppid string `json:"ppid"` +} diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go deleted file mode 100644 index b381fab8eb7..00000000000 --- a/openrtb_ext/imp_nanointeractive.go +++ /dev/null @@ -1,10 +0,0 @@ -package openrtb_ext - -// ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.prebid.bidder.nanointeractive -type ExtImpNanoInteractive struct { - Pid string `json:"pid"` - Nq []string `json:"nq,omitempty"` - Category string `json:"category,omitempty"` - SubId string `json:"subId,omitempty"` - Ref string `json:"ref,omitempty"` -} diff --git a/openrtb_ext/imp_ninthdecimal.go b/openrtb_ext/imp_ninthdecimal.go deleted file mode 100755 index 8fb794dbdf2..00000000000 --- a/openrtb_ext/imp_ninthdecimal.go +++ /dev/null @@ -1,6 +0,0 @@ -package openrtb_ext - -type ExtImpNinthDecimal struct { - PublisherID string `json:"pubid"` - Placement string `json:"placement,omitempty"` -} diff --git a/openrtb_ext/imp_oms.go b/openrtb_ext/imp_oms.go new file mode 100644 index 00000000000..4e12da86264 --- /dev/null +++ b/openrtb_ext/imp_oms.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpOms defines the contract for bidrequest.imp[i].ext.prebid.bidder.oms +type ExtImpOms struct { + PublisherID string `json:"pid"` +} diff --git a/openrtb_ext/imp_ownadx.go b/openrtb_ext/imp_ownadx.go new file mode 100644 index 00000000000..caac600b50e --- /dev/null +++ b/openrtb_ext/imp_ownadx.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpOwnAdx struct { + SspId string `json:"sspId"` + SeatId string `json:"seatId"` + TokenId string `json:"tokenId"` +} diff --git a/openrtb_ext/imp_pgamssp.go b/openrtb_ext/imp_pgamssp.go new file mode 100644 index 00000000000..56e4b8bf7ac --- /dev/null +++ b/openrtb_ext/imp_pgamssp.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtPgamSsp struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_relevantdigital.go b/openrtb_ext/imp_relevantdigital.go new file mode 100644 index 00000000000..ec250557c2b --- /dev/null +++ b/openrtb_ext/imp_relevantdigital.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtRelevantDigital struct { + AccountId string `json:"accountId"` + PlacementId string `json:"placementId"` + Host string `json:"pbsHost"` + PbsBufferMs int `json:"pbsBufferMs"` +} diff --git a/openrtb_ext/imp_rhythmone.go b/openrtb_ext/imp_rhythmone.go deleted file mode 100644 index 526a2843b53..00000000000 --- a/openrtb_ext/imp_rhythmone.go +++ /dev/null @@ -1,9 +0,0 @@ -package openrtb_ext - -// ExtImpRhythmone defines the contract for bidrequest.imp[i].ext.prebid.bidder.rhythmone -type ExtImpRhythmone struct { - PlacementId string `json:"placementId"` - Zone string `json:"zone"` - Path string `json:"path"` - S2S bool -} diff --git a/openrtb_ext/imp_rise.go b/openrtb_ext/imp_rise.go index 4bdd302ee99..7311f05d52d 100644 --- a/openrtb_ext/imp_rise.go +++ b/openrtb_ext/imp_rise.go @@ -3,4 +3,5 @@ package openrtb_ext // ImpExtRise defines the contract for bidrequest.imp[i].ext.prebid.bidder.rise type ImpExtRise struct { PublisherID string `json:"publisher_id"` + Org string `json:"org"` } diff --git a/openrtb_ext/imp_rtbhouse.go b/openrtb_ext/imp_rtbhouse.go new file mode 100644 index 00000000000..747ea158f9d --- /dev/null +++ b/openrtb_ext/imp_rtbhouse.go @@ -0,0 +1,10 @@ +package openrtb_ext + +// ExtImpRTBHouse defines the contract for bidrequest.imp[i].ext.prebid.bidder.rtbhouse +type ExtImpRTBHouse struct { + PublisherId string `json:"publisherId"` + Region string `json:"region"` + + BidFloor float64 `json:"bidfloor,omitempty"` + Channel string `json:"channel,omitempty"` +} diff --git a/openrtb_ext/imp_screencore.go b/openrtb_ext/imp_screencore.go new file mode 100644 index 00000000000..5a3fa1c39ff --- /dev/null +++ b/openrtb_ext/imp_screencore.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtScreencore struct { + AccountID string `json:"accountId"` + PlacementID string `json:"placementId"` +} diff --git a/openrtb_ext/imp_seedingAlliance.go b/openrtb_ext/imp_seedingAlliance.go index 759683ad6b3..0f594d3c933 100644 --- a/openrtb_ext/imp_seedingAlliance.go +++ b/openrtb_ext/imp_seedingAlliance.go @@ -1,5 +1,6 @@ package openrtb_ext type ImpExtSeedingAlliance struct { - AdUnitID string `json:"adUnitID"` + AdUnitID string `json:"adUnitId"` + SeatID string `json:"seatId"` } diff --git a/openrtb_ext/imp_silverpush.go b/openrtb_ext/imp_silverpush.go new file mode 100644 index 00000000000..0a043356332 --- /dev/null +++ b/openrtb_ext/imp_silverpush.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ImpExtSilverpush defines the contract for bidrequest.imp[i].ext.prebid.bidder.silverpush +// PublisherId is mandatory parameters +type ImpExtSilverpush struct { + PublisherId string `json:"publisherId"` + BidFloor float64 `json:"bidfloor"` +} diff --git a/openrtb_ext/imp_smartx.go b/openrtb_ext/imp_smartx.go new file mode 100644 index 00000000000..9a2975b01de --- /dev/null +++ b/openrtb_ext/imp_smartx.go @@ -0,0 +1,10 @@ +package openrtb_ext + +type ExtImpSmartclip struct { + TagID string `json:"tagId"` + PublisherID string `json:"publisherId"` + SiteID string `json:"siteId"` + AppID string `json:"appId"` + BundleID string `json:"bundleId"` + StoreURL string `json:"storeUrl"` +} diff --git a/openrtb_ext/imp_sovrn.go b/openrtb_ext/imp_sovrn.go index 38d2f785803..362463f9513 100644 --- a/openrtb_ext/imp_sovrn.go +++ b/openrtb_ext/imp_sovrn.go @@ -1,7 +1,8 @@ package openrtb_ext type ExtImpSovrn struct { - TagId string `json:"tagId,omitempty"` - Tagid string `json:"tagid,omitempty"` - BidFloor float64 `json:"bidfloor"` + TagId string `json:"tagId,omitempty"` + Tagid string `json:"tagid,omitempty"` + BidFloor float64 `json:"bidfloor,omitempty"` + AdUnitCode string `json:"adunitcode,omitempty"` } diff --git a/openrtb_ext/imp_sovrnXsp.go b/openrtb_ext/imp_sovrnXsp.go new file mode 100644 index 00000000000..90c78d707e4 --- /dev/null +++ b/openrtb_ext/imp_sovrnXsp.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtImpSovrnXsp struct { + PubID string `json:"pub_id,omitempty"` + MedID string `json:"med_id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ForceBid bool `json:"force_bid,omitempty"` +} diff --git a/openrtb_ext/imp_suntContent.go b/openrtb_ext/imp_suntContent.go deleted file mode 100644 index 5040df7e3b6..00000000000 --- a/openrtb_ext/imp_suntContent.go +++ /dev/null @@ -1,5 +0,0 @@ -package openrtb_ext - -type ImpExtSuntContent struct { - AdUnitID string `json:"adUnitID"` -} diff --git a/openrtb_ext/imp_teads.go b/openrtb_ext/imp_teads.go new file mode 100644 index 00000000000..50d11ae9192 --- /dev/null +++ b/openrtb_ext/imp_teads.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpTeads struct { + PlacementID string `json:"placementId"` +} diff --git a/openrtb_ext/imp_tpmn.go b/openrtb_ext/imp_tpmn.go new file mode 100644 index 00000000000..373b3089cc8 --- /dev/null +++ b/openrtb_ext/imp_tpmn.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpTpmn defines TPMN specifiec param +type ExtImpTpmn struct { + InventoryId int `json:"inventoryId"` +} diff --git a/openrtb_ext/imp_vox.go b/openrtb_ext/imp_vox.go new file mode 100644 index 00000000000..a61525077b3 --- /dev/null +++ b/openrtb_ext/imp_vox.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ImpExtVox struct { + PlacementID string `json:"placementId"` + ImageUrl string `json:"imageUrl"` + DisplaySizes []string `json:"displaySizes"` +} diff --git a/openrtb_ext/imp_yahooAds.go b/openrtb_ext/imp_yahooAds.go new file mode 100644 index 00000000000..36a4a0f618b --- /dev/null +++ b/openrtb_ext/imp_yahooAds.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpYahooAds defines the contract for bidrequest.imp[i].ext.prebid.bidder.yahooAds +type ExtImpYahooAds struct { + Dcn string `json:"dcn"` + Pos string `json:"pos"` +} diff --git a/openrtb_ext/multibid_test.go b/openrtb_ext/multibid_test.go index a27631d6891..926ab261b0f 100644 --- a/openrtb_ext/multibid_test.go +++ b/openrtb_ext/multibid_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index adf00fcd169..1a98e34d13f 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,9 +5,10 @@ import ( "fmt" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) // FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. @@ -41,52 +42,54 @@ type ExtRequest struct { // ExtRequestPrebid defines the contract for bidrequest.ext.prebid type ExtRequestPrebid struct { - Aliases map[string]string `json:"aliases,omitempty"` - AliasGVLIDs map[string]uint16 `json:"aliasgvlids,omitempty"` - BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` - BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` - BidderParams json.RawMessage `json:"bidderparams,omitempty"` - Cache *ExtRequestPrebidCache `json:"cache,omitempty"` - Channel *ExtRequestPrebidChannel `json:"channel,omitempty"` - CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` - Data *ExtRequestPrebidData `json:"data,omitempty"` - Debug bool `json:"debug,omitempty"` - Events json.RawMessage `json:"events,omitempty"` - Experiment *Experiment `json:"experiment,omitempty"` - Integration string `json:"integration,omitempty"` - MultiBid []*ExtMultiBid `json:"multibid,omitempty"` - Passthrough json.RawMessage `json:"passthrough,omitempty"` - SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"` - Server *ExtRequestPrebidServer `json:"server,omitempty"` - StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` - SupportDeals bool `json:"supportdeals,omitempty"` - Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + AdServerTargeting []AdServerTarget `json:"adservertargeting,omitempty"` + Aliases map[string]string `json:"aliases,omitempty"` + AliasGVLIDs map[string]uint16 `json:"aliasgvlids,omitempty"` + BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` + BidAdjustments *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"` + BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` + BidderParams json.RawMessage `json:"bidderparams,omitempty"` + Cache *ExtRequestPrebidCache `json:"cache,omitempty"` + Channel *ExtRequestPrebidChannel `json:"channel,omitempty"` + CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` + Data *ExtRequestPrebidData `json:"data,omitempty"` + Debug bool `json:"debug,omitempty"` + Events json.RawMessage `json:"events,omitempty"` + Experiment *Experiment `json:"experiment,omitempty"` + Floors *PriceFloorRules `json:"floors,omitempty"` + Integration string `json:"integration,omitempty"` + MultiBid []*ExtMultiBid `json:"multibid,omitempty"` + MultiBidMap map[string]ExtMultiBid `json:"-"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` + SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"` + Sdk *ExtRequestSdk `json:"sdk,omitempty"` + Server *ExtRequestPrebidServer `json:"server,omitempty"` + StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` + SupportDeals bool `json:"supportdeals,omitempty"` + Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + + //AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request + AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"` + + // Macros specifies list of custom macros along with the values. This is used while forming + // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding + Macros map[string]string `json:"macros,omitempty"` // NoSale specifies bidders with whom the publisher has a legal relationship where the // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` - //AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request - AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"` + // ReturnAllBidStatus if true populates bidresponse.ext.prebid.seatnonbid with all bids which was + // either rejected, nobid, input error + ReturnAllBidStatus bool `json:"returnallbidstatus,omitempty"` - Floors *PriceFloorRules `json:"floors,omitempty"` - MultiBidMap map[string]ExtMultiBid `json:"-"` // Trace controls the level of detail in the output information returned from executing hooks. // There are two options: // - verbose: sets maximum level of output information // - basic: excludes debugmessages and analytic_tags from output // any other value or an empty string disables trace output at all. Trace string `json:"trace,omitempty"` - - // Macros specifies list of custom macros along with the values. This is used while forming - // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding - Macros map[string]string `json:"macros,omitempty"` - AdServerTargeting []AdServerTarget `json:"adservertargeting,omitempty"` - BidAdjustments *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"` - // ReturnAllBidStatus if true populates bidresponse.ext.prebid.seatnonbid with all bids which was - // either rejected, nobid, input error - ReturnAllBidStatus bool `json:"returnallbidstatus,omitempty"` } type AdServerTarget struct { @@ -232,7 +235,7 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { // price granularity used to be a string referencing a predefined value, try to parse // and map the legacy string before falling back to the modern custom model. legacyID := "" - if err := json.Unmarshal(b, &legacyID); err == nil { + if err := jsonutil.Unmarshal(b, &legacyID); err == nil { if legacyValue, ok := NewPriceGranularityFromLegacyID(legacyID); ok { *pg = legacyValue return nil @@ -241,7 +244,7 @@ func (pg *PriceGranularity) UnmarshalJSON(b []byte) error { // use a type-alias to avoid calling back into this UnmarshalJSON implementation modernValue := PriceGranularityRaw{} - err := json.Unmarshal(b, &modernValue) + err := jsonutil.Unmarshal(b, &modernValue) if err == nil { *pg = (PriceGranularity)(modernValue) } @@ -345,6 +348,16 @@ type ExtRequestPrebidDataEidPermission struct { Bidders []string `json:"bidders"` } +type ExtRequestSdk struct { + Renderers []ExtRequestSdkRenderer `json:"renderers,omitempty"` +} + +type ExtRequestSdkRenderer struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + type ExtMultiBid struct { Bidder string `json:"bidder,omitempty"` Bidders []string `json:"bidders,omitempty"` diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index a05bae3a6bf..3163150e57c 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -6,7 +6,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -37,7 +38,7 @@ func TestGranularityUnmarshal(t *testing.T) { for _, tg := range testGroups { for i, tc := range tg.in { var resolved PriceGranularity - err := json.Unmarshal(tc.json, &resolved) + err := jsonutil.UnmarshalValid(tc.json, &resolved) // Assert validation error if tg.expectError && !assert.Errorf(t, err, "%s test case %d", tg.desc, i) { diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 5f5636d602a..5adc192d36f 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -5,9 +5,10 @@ import ( "errors" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) // RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they @@ -42,6 +43,7 @@ type RequestWrapper struct { appExt *AppExt regExt *RegExt siteExt *SiteExt + doohExt *DOOHExt sourceExt *SourceExt } @@ -160,6 +162,17 @@ func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) { return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext) } +func (rw *RequestWrapper) GetDOOHExt() (*DOOHExt, error) { + if rw.doohExt != nil { + return rw.doohExt, nil + } + rw.doohExt = &DOOHExt{} + if rw.BidRequest == nil || rw.DOOH == nil || rw.DOOH.Ext == nil { + return rw.doohExt, rw.doohExt.unmarshal(json.RawMessage{}) + } + return rw.doohExt, rw.doohExt.unmarshal(rw.DOOH.Ext) +} + func (rw *RequestWrapper) GetSourceExt() (*SourceExt, error) { if rw.sourceExt != nil { return rw.sourceExt, nil @@ -197,6 +210,9 @@ func (rw *RequestWrapper) RebuildRequest() error { if err := rw.rebuildSiteExt(); err != nil { return err } + if err := rw.rebuildDOOHExt(); err != nil { + return err + } if err := rw.rebuildSourceExt(); err != nil { return err } @@ -297,6 +313,25 @@ func (rw *RequestWrapper) rebuildAppExt() error { return nil } +func (rw *RequestWrapper) rebuildDOOHExt() error { + if rw.doohExt == nil || !rw.doohExt.Dirty() { + return nil + } + + doohJson, err := rw.doohExt.marshal() + if err != nil { + return err + } + + if doohJson != nil && rw.DOOH == nil { + rw.DOOH = &openrtb2.DOOH{Ext: doohJson} + } else if rw.DOOH != nil { + rw.DOOH.Ext = doohJson + } + + return nil +} + func (rw *RequestWrapper) rebuildRegExt() error { if rw.regExt == nil || !rw.regExt.Dirty() { return nil @@ -370,6 +405,7 @@ func (rw *RequestWrapper) Clone() *RequestWrapper { clone.appExt = rw.appExt.Clone() clone.regExt = rw.regExt.Clone() clone.siteExt = rw.siteExt.Clone() + clone.doohExt = rw.doohExt.Clone() clone.sourceExt = rw.sourceExt.Clone() return &clone @@ -405,13 +441,13 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &ue.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &ue.ext); err != nil { return err } consentJson, hasConsent := ue.ext[consentKey] - if hasConsent { - if err := json.Unmarshal(consentJson, &ue.consent); err != nil { + if hasConsent && consentJson != nil { + if err := jsonutil.Unmarshal(consentJson, &ue.consent); err != nil { return err } } @@ -419,7 +455,9 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { prebidJson, hasPrebid := ue.ext[prebidKey] if hasPrebid { ue.prebid = &ExtUserPrebid{} - if err := json.Unmarshal(prebidJson, ue.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, ue.prebid); err != nil { return err } } @@ -427,21 +465,29 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { eidsJson, hasEids := ue.ext[eidsKey] if hasEids { ue.eids = &[]openrtb2.EID{} - if err := json.Unmarshal(eidsJson, ue.eids); err != nil { + } + if eidsJson != nil { + if err := jsonutil.Unmarshal(eidsJson, ue.eids); err != nil { return err } } - if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsStringKey]; hasCPSettings { + consentedProviderSettingsInJson, hasCPSettingsIn := ue.ext[consentedProvidersSettingsStringKey] + if hasCPSettingsIn { ue.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{} - if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsIn); err != nil { + } + if consentedProviderSettingsInJson != nil { + if err := jsonutil.Unmarshal(consentedProviderSettingsInJson, ue.consentedProvidersSettingsIn); err != nil { return err } } - if consentedProviderSettingsJson, hasCPSettings := ue.ext[consentedProvidersSettingsListKey]; hasCPSettings { + consentedProviderSettingsOutJson, hasCPSettingsOut := ue.ext[consentedProvidersSettingsListKey] + if hasCPSettingsOut { ue.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{} - if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsOut); err != nil { + } + if consentedProviderSettingsOutJson != nil { + if err := jsonutil.Unmarshal(consentedProviderSettingsOutJson, ue.consentedProvidersSettingsOut); err != nil { return err } } @@ -452,7 +498,7 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error { func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentDirty { if ue.consent != nil && len(*ue.consent) > 0 { - consentJson, err := json.Marshal(ue.consent) + consentJson, err := jsonutil.Marshal(ue.consent) if err != nil { return nil, err } @@ -465,7 +511,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.prebidDirty { if ue.prebid != nil { - prebidJson, err := json.Marshal(ue.prebid) + prebidJson, err := jsonutil.Marshal(ue.prebid) if err != nil { return nil, err } @@ -482,7 +528,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentedProvidersSettingsInDirty { if ue.consentedProvidersSettingsIn != nil { - cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsIn) + cpSettingsJson, err := jsonutil.Marshal(ue.consentedProvidersSettingsIn) if err != nil { return nil, err } @@ -499,7 +545,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.consentedProvidersSettingsOutDirty { if ue.consentedProvidersSettingsOut != nil { - cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsOut) + cpSettingsJson, err := jsonutil.Marshal(ue.consentedProvidersSettingsOut) if err != nil { return nil, err } @@ -516,7 +562,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if ue.eidsDirty { if ue.eids != nil && len(*ue.eids) > 0 { - eidsJson, err := json.Marshal(ue.eids) + eidsJson, err := jsonutil.Marshal(ue.eids) if err != nil { return nil, err } @@ -531,7 +577,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) { if len(ue.ext) == 0 { return nil, nil } - return json.Marshal(ue.ext) + return jsonutil.Marshal(ue.ext) } func (ue *UserExt) Dirty() bool { @@ -691,14 +737,16 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &re.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &re.ext); err != nil { return err } prebidJson, hasPrebid := re.ext[prebidKey] if hasPrebid { re.prebid = &ExtRequestPrebid{} - if err := json.Unmarshal(prebidJson, re.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, re.prebid); err != nil { return err } } @@ -706,7 +754,9 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { schainJson, hasSChain := re.ext[schainKey] if hasSChain { re.schain = &openrtb2.SupplyChain{} - if err := json.Unmarshal(schainJson, re.schain); err != nil { + } + if schainJson != nil { + if err := jsonutil.Unmarshal(schainJson, re.schain); err != nil { return err } } @@ -717,7 +767,7 @@ func (re *RequestExt) unmarshal(extJson json.RawMessage) error { func (re *RequestExt) marshal() (json.RawMessage, error) { if re.prebidDirty { if re.prebid != nil { - prebidJson, err := json.Marshal(re.prebid) + prebidJson, err := jsonutil.Marshal(re.prebid) if err != nil { return nil, err } @@ -734,7 +784,7 @@ func (re *RequestExt) marshal() (json.RawMessage, error) { if re.schainDirty { if re.schain != nil { - schainJson, err := json.Marshal(re.schain) + schainJson, err := jsonutil.Marshal(re.schain) if err != nil { return nil, err } @@ -753,7 +803,7 @@ func (re *RequestExt) marshal() (json.RawMessage, error) { if len(re.ext) == 0 { return nil, nil } - return json.Marshal(re.ext) + return jsonutil.Marshal(re.ext) } func (re *RequestExt) Dirty() bool { @@ -845,14 +895,16 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &de.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &de.ext); err != nil { return err } prebidJson, hasPrebid := de.ext[prebidKey] if hasPrebid { de.prebid = &ExtDevicePrebid{} - if err := json.Unmarshal(prebidJson, de.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, de.prebid); err != nil { return err } } @@ -863,7 +915,7 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { func (de *DeviceExt) marshal() (json.RawMessage, error) { if de.prebidDirty { if de.prebid != nil { - prebidJson, err := json.Marshal(de.prebid) + prebidJson, err := jsonutil.Marshal(de.prebid) if err != nil { return nil, err } @@ -882,7 +934,7 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) { if len(de.ext) == 0 { return nil, nil } - return json.Marshal(de.ext) + return jsonutil.Marshal(de.ext) } func (de *DeviceExt) Dirty() bool { @@ -957,14 +1009,16 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &ae.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &ae.ext); err != nil { return err } prebidJson, hasPrebid := ae.ext[prebidKey] if hasPrebid { ae.prebid = &ExtAppPrebid{} - if err := json.Unmarshal(prebidJson, ae.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, ae.prebid); err != nil { return err } } @@ -975,7 +1029,7 @@ func (ae *AppExt) unmarshal(extJson json.RawMessage) error { func (ae *AppExt) marshal() (json.RawMessage, error) { if ae.prebidDirty { if ae.prebid != nil { - prebidJson, err := json.Marshal(ae.prebid) + prebidJson, err := jsonutil.Marshal(ae.prebid) if err != nil { return nil, err } @@ -994,7 +1048,7 @@ func (ae *AppExt) marshal() (json.RawMessage, error) { if len(ae.ext) == 0 { return nil, nil } - return json.Marshal(ae.ext) + return jsonutil.Marshal(ae.ext) } func (ae *AppExt) Dirty() bool { @@ -1040,6 +1094,70 @@ func (ae *AppExt) Clone() *AppExt { return &clone } +// --------------------------------------------------------------- +// DOOHExt provides an interface for request.dooh.ext +// This is currently a placeholder for consistency with others - no useful attributes and getters/setters exist yet +// --------------------------------------------------------------- + +type DOOHExt struct { + ext map[string]json.RawMessage + extDirty bool +} + +func (de *DOOHExt) unmarshal(extJson json.RawMessage) error { + if len(de.ext) != 0 || de.Dirty() { + return nil + } + + de.ext = make(map[string]json.RawMessage) + + if len(extJson) == 0 { + return nil + } + + if err := jsonutil.Unmarshal(extJson, &de.ext); err != nil { + return err + } + + return nil +} + +func (de *DOOHExt) marshal() (json.RawMessage, error) { + de.extDirty = false + if len(de.ext) == 0 { + return nil, nil + } + return jsonutil.Marshal(de.ext) +} + +func (de *DOOHExt) Dirty() bool { + return de.extDirty +} + +func (de *DOOHExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range de.ext { + ext[k] = v + } + return ext +} + +func (de *DOOHExt) SetExt(ext map[string]json.RawMessage) { + de.ext = ext + de.extDirty = true +} + +func (de *DOOHExt) Clone() *DOOHExt { + if de == nil { + return nil + } + + clone := *de + clone.ext = maputil.Clone(de.ext) + + return &clone +} + // --------------------------------------------------------------- // RegExt provides an interface for request.regs.ext // --------------------------------------------------------------- @@ -1064,20 +1182,20 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &re.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &re.ext); err != nil { return err } gdprJson, hasGDPR := re.ext[gdprKey] - if hasGDPR { - if err := json.Unmarshal(gdprJson, &re.gdpr); err != nil { + if hasGDPR && gdprJson != nil { + if err := jsonutil.Unmarshal(gdprJson, &re.gdpr); err != nil { return errors.New("gdpr must be an integer") } } uspJson, hasUsp := re.ext[us_privacyKey] - if hasUsp { - if err := json.Unmarshal(uspJson, &re.usPrivacy); err != nil { + if hasUsp && uspJson != nil { + if err := jsonutil.Unmarshal(uspJson, &re.usPrivacy); err != nil { return err } } @@ -1088,7 +1206,7 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { func (re *RegExt) marshal() (json.RawMessage, error) { if re.gdprDirty { if re.gdpr != nil { - rawjson, err := json.Marshal(re.gdpr) + rawjson, err := jsonutil.Marshal(re.gdpr) if err != nil { return nil, err } @@ -1101,7 +1219,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { if re.usPrivacyDirty { if len(re.usPrivacy) > 0 { - rawjson, err := json.Marshal(re.usPrivacy) + rawjson, err := jsonutil.Marshal(re.usPrivacy) if err != nil { return nil, err } @@ -1116,7 +1234,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { if len(re.ext) == 0 { return nil, nil } - return json.Marshal(re.ext) + return jsonutil.Marshal(re.ext) } func (re *RegExt) Dirty() bool { @@ -1191,13 +1309,13 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &se.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &se.ext); err != nil { return err } ampJson, hasAmp := se.ext[ampKey] - if hasAmp { - if err := json.Unmarshal(ampJson, &se.amp); err != nil { + if hasAmp && ampJson != nil { + if err := jsonutil.Unmarshal(ampJson, &se.amp); err != nil { return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } } @@ -1208,7 +1326,7 @@ func (se *SiteExt) unmarshal(extJson json.RawMessage) error { func (se *SiteExt) marshal() (json.RawMessage, error) { if se.ampDirty { if se.amp != nil { - ampJson, err := json.Marshal(se.amp) + ampJson, err := jsonutil.Marshal(se.amp) if err != nil { return nil, err } @@ -1223,7 +1341,7 @@ func (se *SiteExt) marshal() (json.RawMessage, error) { if len(se.ext) == 0 { return nil, nil } - return json.Marshal(se.ext) + return jsonutil.Marshal(se.ext) } func (se *SiteExt) Dirty() bool { @@ -1286,13 +1404,13 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &se.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &se.ext); err != nil { return err } schainJson, hasSChain := se.ext[schainKey] - if hasSChain { - if err := json.Unmarshal(schainJson, &se.schain); err != nil { + if hasSChain && schainJson != nil { + if err := jsonutil.Unmarshal(schainJson, &se.schain); err != nil { return err } } @@ -1303,7 +1421,7 @@ func (se *SourceExt) unmarshal(extJson json.RawMessage) error { func (se *SourceExt) marshal() (json.RawMessage, error) { if se.schainDirty { if se.schain != nil { - schainJson, err := json.Marshal(se.schain) + schainJson, err := jsonutil.Marshal(se.schain) if err != nil { return nil, err } @@ -1322,7 +1440,7 @@ func (se *SourceExt) marshal() (json.RawMessage, error) { if len(se.ext) == 0 { return nil, nil } - return json.Marshal(se.ext) + return jsonutil.Marshal(se.ext) } func (se *SourceExt) Dirty() bool { @@ -1451,14 +1569,16 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { return nil } - if err := json.Unmarshal(extJson, &e.ext); err != nil { + if err := jsonutil.Unmarshal(extJson, &e.ext); err != nil { return err } prebidJson, hasPrebid := e.ext[prebidKey] if hasPrebid { e.prebid = &ExtImpPrebid{} - if err := json.Unmarshal(prebidJson, e.prebid); err != nil { + } + if prebidJson != nil { + if err := jsonutil.Unmarshal(prebidJson, e.prebid); err != nil { return err } } @@ -1466,21 +1586,23 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { dataJson, hasData := e.ext[dataKey] if hasData { e.data = &ExtImpData{} - if err := json.Unmarshal(dataJson, e.data); err != nil { + } + if dataJson != nil { + if err := jsonutil.Unmarshal(dataJson, e.data); err != nil { return err } } tidJson, hasTid := e.ext["tid"] - if hasTid { - if err := json.Unmarshal(tidJson, &e.tid); err != nil { + if hasTid && tidJson != nil { + if err := jsonutil.Unmarshal(tidJson, &e.tid); err != nil { return err } } gpIdJson, hasGpId := e.ext["gpid"] - if hasGpId { - if err := json.Unmarshal(gpIdJson, &e.gpId); err != nil { + if hasGpId && gpIdJson != nil { + if err := jsonutil.Unmarshal(gpIdJson, &e.gpId); err != nil { return err } } @@ -1491,7 +1613,7 @@ func (e *ImpExt) unmarshal(extJson json.RawMessage) error { func (e *ImpExt) marshal() (json.RawMessage, error) { if e.prebidDirty { if e.prebid != nil { - prebidJson, err := json.Marshal(e.prebid) + prebidJson, err := jsonutil.Marshal(e.prebid) if err != nil { return nil, err } @@ -1508,7 +1630,7 @@ func (e *ImpExt) marshal() (json.RawMessage, error) { if e.tidDirty { if len(e.tid) > 0 { - tidJson, err := json.Marshal(e.tid) + tidJson, err := jsonutil.Marshal(e.tid) if err != nil { return nil, err } @@ -1523,7 +1645,7 @@ func (e *ImpExt) marshal() (json.RawMessage, error) { if len(e.ext) == 0 { return nil, nil } - return json.Marshal(e.ext) + return jsonutil.Marshal(e.ext) } func (e *ImpExt) Dirty() bool { diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index e02dd741519..bd55c86beb2 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -5,7 +5,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -41,6 +42,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, reqWrapCopy: &RequestWrapper{ @@ -60,6 +62,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, mutator: func(t *testing.T, reqWrap *RequestWrapper) {}, @@ -83,6 +86,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, reqWrapCopy: &RequestWrapper{ @@ -102,6 +106,7 @@ func TestCloneRequestWrapper(t *testing.T) { appExt: &AppExt{prebidDirty: true}, regExt: &RegExt{usPrivacy: "foo"}, siteExt: &SiteExt{amp: ptrutil.ToPtr[int8](1)}, + doohExt: &DOOHExt{}, sourceExt: &SourceExt{schainDirty: true}, }, mutator: func(t *testing.T, reqWrap *RequestWrapper) { @@ -115,6 +120,7 @@ func TestCloneRequestWrapper(t *testing.T) { reqWrap.appExt = nil reqWrap.regExt = nil reqWrap.siteExt = nil + reqWrap.doohExt = nil reqWrap.sourceExt = nil }, }, @@ -1206,6 +1212,141 @@ func TestCloneAppExt(t *testing.T) { } } +func TestRebuildDOOHExt(t *testing.T) { + // These permutations look a bit wonky + // Since DOOHExt currently exists for consistency but there isn't a single field + // expected - hence unable to test dirty and variations + // Once one is established, updated the permutations below similar to TestRebuildAppExt example + testCases := []struct { + description string + request openrtb2.BidRequest + requestDOOHExtWrapper DOOHExt + expectedRequest openrtb2.BidRequest + }{ + { + description: "Nil - Not Dirty", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Nil - Dirty", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: nil}, + }, + { + description: "Nil - Dirty - No Change", + request: openrtb2.BidRequest{}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + description: "Empty - Not Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Empty - Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Empty - Dirty - No Change", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{}}, + }, + { + description: "Populated - Not Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty - No Change", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + { + description: "Populated - Dirty - Cleared", + request: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + requestDOOHExtWrapper: DOOHExt{}, + expectedRequest: openrtb2.BidRequest{DOOH: &openrtb2.DOOH{Ext: json.RawMessage(`{}`)}}, + }, + } + + for _, test := range testCases { + // create required filed in the test loop to keep test declaration easier to read + test.requestDOOHExtWrapper.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &test.request, doohExt: &test.requestDOOHExtWrapper} + w.RebuildRequest() + assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) + } +} + +func TestCloneDOOHExt(t *testing.T) { + testCases := []struct { + name string + DOOHExt *DOOHExt + DOOHExtCopy *DOOHExt // manual copy of above ext object to verify against + mutator func(t *testing.T, DOOHExt *DOOHExt) // function to modify the Ext object + }{ + { + name: "Nil", // Verify the nil case + DOOHExt: nil, + DOOHExtCopy: nil, + mutator: func(t *testing.T, DOOHExt *DOOHExt) {}, + }, + { + name: "NoMutate", + DOOHExt: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + DOOHExtCopy: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + mutator: func(t *testing.T, DOOHExt *DOOHExt) {}, + }, + { + name: "General", + DOOHExt: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + DOOHExtCopy: &DOOHExt{ + ext: map[string]json.RawMessage{"A": json.RawMessage(`X`), "B": json.RawMessage(`Y`)}, + extDirty: true, + }, + mutator: func(t *testing.T, DOOHExt *DOOHExt) { + DOOHExt.ext["A"] = json.RawMessage(`"string"`) + DOOHExt.ext["C"] = json.RawMessage(`{}`) + DOOHExt.extDirty = false + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + clone := test.DOOHExt.Clone() + test.mutator(t, test.DOOHExt) + assert.Equal(t, test.DOOHExtCopy, clone) + }) + } +} + func TestCloneRegExt(t *testing.T) { testCases := []struct { name string @@ -1652,10 +1793,10 @@ func TestImpWrapperGetImpExt(t *testing.T) { var isRewardedInventoryOne int8 = 1 testCases := []struct { - description string - givenWrapper ImpWrapper - expectedImpExt ImpExt - expectedError string + description string + givenWrapper ImpWrapper + expectedImpExt ImpExt + expectedErrorType error }{ { description: "Empty", @@ -1693,21 +1834,21 @@ func TestImpWrapperGetImpExt(t *testing.T) { expectedImpExt: ImpExt{ext: map[string]json.RawMessage{"foo": json.RawMessage("bar")}}, }, { - description: "Error - Ext", - givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}}, - expectedError: "invalid character 'm' looking for beginning of value", + description: "Error - Ext", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`malformed`)}}, + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, { - description: "Error - Ext - Prebid", - givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}}, - expectedError: "invalid character 'm' looking for beginning of value", + description: "Error - Ext - Prebid", + givenWrapper: ImpWrapper{Imp: &openrtb2.Imp{Ext: json.RawMessage(`{"prebid":malformed}`)}}, + expectedErrorType: &errortypes.FailedToUnmarshal{}, }, } for _, test := range testCases { impExt, err := test.givenWrapper.GetImpExt() - if test.expectedError != "" { - assert.EqualError(t, err, test.expectedError, test.description) + if test.expectedErrorType != nil { + assert.IsType(t, test.expectedErrorType, err) } else { assert.NoError(t, err, test.description) assert.Equal(t, test.expectedImpExt, *impExt, test.description) diff --git a/openrtb_ext/site_test.go b/openrtb_ext/site_test.go index 67ec6cc4f99..f6fb04c50ee 100644 --- a/openrtb_ext/site_test.go +++ b/openrtb_ext/site_test.go @@ -1,28 +1,28 @@ package openrtb_ext_test import ( - "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestInvalidSiteExt(t *testing.T) { var s openrtb_ext.ExtSite - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":-1}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":2}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":true}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":null}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") - assert.EqualError(t, json.Unmarshal([]byte(`{"amp":"1"}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":-1}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":2}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":true}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":null}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") + assert.EqualError(t, jsonutil.UnmarshalValid([]byte(`{"amp":"1"}`), &s), "request.site.ext.amp must be either 1, 0, or undefined") } func TestValidSiteExt(t *testing.T) { var s openrtb_ext.ExtSite - assert.NoError(t, json.Unmarshal([]byte(`{"amp":0}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp":0}`), &s)) assert.EqualValues(t, 0, s.AMP) - assert.NoError(t, json.Unmarshal([]byte(`{"amp":1}`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp":1}`), &s)) assert.EqualValues(t, 1, s.AMP) - assert.NoError(t, json.Unmarshal([]byte(`{"amp": 1 }`), &s)) + assert.NoError(t, jsonutil.UnmarshalValid([]byte(`{"amp": 1 }`), &s)) assert.EqualValues(t, 1, s.AMP) } diff --git a/openrtb_ext/supplyChain.go b/openrtb_ext/supplyChain.go index 6f023542dfb..0ccbd0957fa 100644 --- a/openrtb_ext/supplyChain.go +++ b/openrtb_ext/supplyChain.go @@ -2,7 +2,7 @@ package openrtb_ext import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) func cloneSupplyChain(schain *openrtb2.SupplyChain) *openrtb2.SupplyChain { diff --git a/openrtb_ext/supplyChain_test.go b/openrtb_ext/supplyChain_test.go index 12fd5c337fb..728fbc68cc4 100644 --- a/openrtb_ext/supplyChain_test.go +++ b/openrtb_ext/supplyChain_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/ortb/clone.go b/ortb/clone.go index c0e5a4ddada..2fe9be68f74 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -2,8 +2,9 @@ package ortb import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) func CloneApp(s *openrtb2.App) *openrtb2.App { @@ -189,6 +190,140 @@ func CloneUser(s *openrtb2.User) *openrtb2.User { return &c } +func CloneDevice(s *openrtb2.Device) *openrtb2.Device { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Geo = CloneGeo(s.Geo) + + c.DNT = CloneInt8Pointer(s.DNT) + c.Lmt = CloneInt8Pointer(s.Lmt) + + c.SUA = CloneUserAgent(s.SUA) + if s.ConnectionType != nil { + connectionTypeCopy := s.ConnectionType.Val() + c.ConnectionType = &connectionTypeCopy + } + + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneInt8Pointer(s *int8) *int8 { + if s == nil { + return nil + } + var dntCopy int8 + dntCopy = *s + return &dntCopy +} + +func CloneUserAgent(s *openrtb2.UserAgent) *openrtb2.UserAgent { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Browsers = CloneBrandVersionSlice(s.Browsers) + c.Platform = CloneBrandVersion(s.Platform) + + if s.Mobile != nil { + mobileCopy := *s.Mobile + c.Mobile = &mobileCopy + } + s.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneBrandVersionSlice(s []openrtb2.BrandVersion) []openrtb2.BrandVersion { + if s == nil { + return nil + } + + c := make([]openrtb2.BrandVersion, len(s)) + for i, d := range s { + bv := CloneBrandVersion(&d) + c[i] = *bv + } + + return c +} + +func CloneBrandVersion(s *openrtb2.BrandVersion) *openrtb2.BrandVersion { + if s == nil { + return nil + } + c := *s + + // Deep Copy (Pointers) + c.Version = sliceutil.Clone(s.Version) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSource(s *openrtb2.Source) *openrtb2.Source { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.SChain = CloneSChain(s.SChain) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSChain(s *openrtb2.SupplyChain) *openrtb2.SupplyChain { + if s == nil { + return nil + } + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) + c.Nodes = CloneSupplyChainNodes(s.Nodes) + c.Ext = sliceutil.Clone(s.Ext) + + return &c +} + +func CloneSupplyChainNodes(s []openrtb2.SupplyChainNode) []openrtb2.SupplyChainNode { + if s == nil { + return nil + } + + c := make([]openrtb2.SupplyChainNode, len(s)) + for i, d := range s { + c[i] = CloneSupplyChainNode(d) + } + + return c +} + +func CloneSupplyChainNode(s openrtb2.SupplyChainNode) openrtb2.SupplyChainNode { + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + + // Deep Copy (Pointers) + s.HP = CloneInt8Pointer(s.HP) + s.Ext = sliceutil.Clone(s.Ext) + + return s +} + func CloneGeo(s *openrtb2.Geo) *openrtb2.Geo { if s == nil { return nil @@ -265,3 +400,33 @@ func CloneDOOH(s *openrtb2.DOOH) *openrtb2.DOOH { return &c } + +// cloneBidderReq - clones bidder request and replaces req.User and req.Device and req.Source with new copies +func CloneBidderReq(req *openrtb2.BidRequest) *openrtb_ext.RequestWrapper { + if req == nil { + return nil + } + + // bidder request may be modified differently per bidder based on privacy configs + // new request should be created for each bidder request + // pointer fields like User and Device should be cloned and set back to the request copy + newReq := ptrutil.Clone(req) + + if req.User != nil { + userCopy := CloneUser(req.User) + newReq.User = userCopy + } + + if req.Device != nil { + deviceCopy := CloneDevice(req.Device) + newReq.Device = deviceCopy + } + + if req.Source != nil { + sourceCopy := CloneSource(req.Source) + newReq.Source = sourceCopy + } + + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: newReq} + return reqWrapper +} diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 24e43bda1e5..1afc269240b 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -7,7 +7,7 @@ import ( "github.com/prebid/openrtb/v19/adcom1" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -536,6 +536,384 @@ func TestCloneUser(t *testing.T) { }) } +func TestCloneDevice(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneDevice(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Device{} + result := CloneDevice(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + ct := adcom1.ConnectionWIFI + + given := &openrtb2.Device{ + Geo: &openrtb2.Geo{Lat: 1.2, Lon: 2.3, Ext: json.RawMessage(`{"geo":1}`)}, + DNT: np, + Lmt: np, + UA: "UserAgent", + SUA: &openrtb2.UserAgent{Mobile: np, Model: "iPad"}, + IP: "127.0.0.1", + IPv6: "2001::", + DeviceType: adcom1.DeviceTablet, + Make: "Apple", + Model: "iPad", + OS: "macOS", + OSV: "1.2.3", + HWV: "mini", + H: 20, + W: 30, + PPI: 100, + PxRatio: 200, + JS: 2, + GeoFetch: 4, + FlashVer: "1.22.33", + Language: "En", + LangB: "ENG", + Carrier: "AT&T", + MCCMNC: "111-222", + ConnectionType: &ct, + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DIDMD5: "DIDMD5", + DPIDSHA1: "DPIDSHA1", + DPIDMD5: "DPIDMD5", + MACSHA1: "MACSHA1", + MACMD5: "MACMD5", + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneDevice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Geo, result.Geo, "geo") + assert.NotSame(t, given.Geo.Ext, result.Geo.Ext, "geo-ext") + assert.NotSame(t, given.DNT, result.DNT, "dnt") + assert.NotSame(t, given.Lmt, result.Lmt, "lmt") + assert.NotSame(t, given.SUA, result.SUA, "sua") + assert.NotSame(t, given.ConnectionType, result.ConnectionType, "connectionType") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Device{})), + []string{ + "Geo", + "DNT", + "Lmt", + "SUA", + "ConnectionType", + "Ext", + }) + }) +} + +func TestCloneInt8Pointer(t *testing.T) { + + t.Run("nil", func(t *testing.T) { + result := CloneInt8Pointer(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + var given *int8 + result := CloneInt8Pointer(given) + assert.Nil(t, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + given := &n + result := CloneInt8Pointer(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + }) +} + +func TestCloneUserAgent(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneUserAgent(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.UserAgent{} + result := CloneUserAgent(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + + given := &openrtb2.UserAgent{ + Browsers: []openrtb2.BrandVersion{{Brand: "Apple"}}, + Platform: &openrtb2.BrandVersion{Brand: "Apple"}, + Mobile: np, + Architecture: "X86", + Bitness: "64", + Model: "iPad", + Source: adcom1.UASourceLowEntropy, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneUserAgent(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Browsers, result.Browsers, "browsers") + assert.NotSame(t, given.Platform, result.Platform, "platform") + assert.NotSame(t, given.Mobile, result.Mobile, "mobile") + assert.NotSame(t, given.Architecture, result.Architecture, "architecture") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.UserAgent{})), + []string{ + "Browsers", + "Platform", + "Mobile", + "Ext", + }) + }) +} + +func TestCloneBrandVersionSlice(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBrandVersionSlice(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := []openrtb2.BrandVersion{} + result := CloneBrandVersionSlice(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("one", func(t *testing.T) { + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneBrandVersionSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "2", Version: []string{"s3", "s4"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "3", Version: []string{"s5", "s6"}, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneBrandVersionSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[2], result[2], "item1-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item1-pointer-ext") + }) +} + +func TestCloneBrandVersion(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBrandVersion(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.BrandVersion{} + result := CloneBrandVersion(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.BrandVersion{ + Brand: "Apple", + Version: []string{"s1"}, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneBrandVersion(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.BrandVersion{})), + []string{ + "Version", + "Ext", + }) + }) +} + +func TestCloneSource(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneSource(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Source{} + result := CloneSource(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + + given := &openrtb2.Source{ + FD: 1, + TID: "Tid", + PChain: "PChain", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":2}`), + }, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSource(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.SChain, result.SChain, "schain") + assert.NotSame(t, given.SChain.Ext, result.SChain.Ext, "schain.ext") + assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.SChain.Nodes[0].Ext, result.SChain.Nodes[0].Ext, "schain.nodes.ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Source{})), + []string{ + "SChain", + "Ext", + }) + }) +} + +func TestCloneSChain(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneSource(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.SupplyChain{} + result := CloneSChain(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSChain(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Nodes, result.Nodes, "nodes") + assert.NotSame(t, given.Nodes[0].Ext, result.Nodes[0].Ext, "nodes.ext") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChain{})), + []string{ + "Nodes", + "Ext", + }) + }) +} + +func TestCloneSupplyChainNodes(t *testing.T) { + var n int8 = 1 + np := &n + t.Run("nil", func(t *testing.T) { + result := CloneSupplyChainNodes(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{} + result := CloneSupplyChainNodes(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("one", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].HP, result[0].HP, "item-pointer-hp") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[0].HP, result[0].HP, "item0-pointer-hp") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[1].HP, result[1].HP, "item1-pointer-hp") + assert.NotSame(t, given[2], result[2], "item2-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item2-pointer-ext") + assert.NotSame(t, given[2].HP, result[2].HP, "item2-pointer-hp") + }) +} + +func TestCloneSupplyChainNode(t *testing.T) { + t.Run("populated", func(t *testing.T) { + var n int8 = 1 + np := &n + + given := openrtb2.SupplyChainNode{ + ASI: "asi", + HP: np, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneSupplyChainNode(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.HP, result.HP, "hp") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChainNode{})), + []string{ + "HP", + "Ext", + }) + }) +} + func TestCloneGeo(t *testing.T) { t.Run("nil", func(t *testing.T) { result := CloneGeo(nil) @@ -749,6 +1127,58 @@ func TestCloneDOOH(t *testing.T) { }) } +func TestCloneBidderReq(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneBidderReq(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.BidRequest{} + result := CloneBidderReq(given) + assert.Equal(t, given, result.BidRequest) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.BidRequest{ + ID: "anyID", + User: &openrtb2.User{ID: "testUserId"}, + Device: &openrtb2.Device{Carrier: "testCarrier"}, + Source: &openrtb2.Source{TID: "testTID"}, + } + result := CloneBidderReq(given) + assert.Equal(t, given, result.BidRequest) + assert.NotSame(t, given, result.BidRequest, "pointer") + assert.NotSame(t, given.User, result.User, "user") + assert.NotSame(t, given.Device, result.Device, "device") + assert.NotSame(t, given.Source, result.Source, "source") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.BidRequest{})), + []string{ + "Device", + "User", + "Source", + "Imp", + "Site", + "App", + "DOOH", + "WSeat", + "BSeat", + "Cur", + "WLang", + "WLangB", + "BCat", + "BAdv", + "BApp", + "Regs", + "Ext", + }) + }) +} + // discoverPointerFields returns the names of all fields of an object that are // pointers and would need to be cloned. This method is specific to types which can // appear within an OpenRTB data model object. diff --git a/ortb/default.go b/ortb/default.go index cd9d8c24759..c5e43e2e770 100644 --- a/ortb/default.go +++ b/ortb/default.go @@ -1,8 +1,8 @@ package ortb import ( - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) const ( diff --git a/ortb/default_test.go b/ortb/default_test.go index 04eeeebdcb6..2d99da4bec2 100644 --- a/ortb/default_test.go +++ b/ortb/default_test.go @@ -8,8 +8,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/ptrutil" ) func TestSetDefaults(t *testing.T) { @@ -31,7 +33,7 @@ func TestSetDefaults(t *testing.T) { name: "malformed request.ext", givenRequest: openrtb2.BidRequest{Ext: json.RawMessage(`malformed`)}, expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`malformed`)}, - expectedErr: "invalid character 'm' looking for beginning of value", + expectedErr: "expect { or n, but found m", }, { name: "targeting", // tests integration with setDefaultsTargeting @@ -55,6 +57,7 @@ func TestSetDefaults(t *testing.T) { // assert error if len(test.expectedErr) > 0 { assert.EqualError(t, err, test.expectedErr, "Error") + assert.IsType(t, &errortypes.FailedToUnmarshal{}, err) } // rebuild request @@ -66,10 +69,10 @@ func TestSetDefaults(t *testing.T) { assert.Equal(t, &test.expectedRequest, wrapper.BidRequest, "Request") } else { // assert request as json to ignore order in ext fields - expectedRequestJSON, err := json.Marshal(test.expectedRequest) + expectedRequestJSON, err := jsonutil.Marshal(test.expectedRequest) require.NoError(t, err, "Marshal Expected Request") - actualRequestJSON, err := json.Marshal(wrapper.BidRequest) + actualRequestJSON, err := jsonutil.Marshal(wrapper.BidRequest) require.NoError(t, err, "Marshal Actual Request") assert.JSONEq(t, string(expectedRequestJSON), string(actualRequestJSON), "Request") diff --git a/pbs/usersync.go b/pbs/usersync.go index 748581af759..bfe12689177 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -10,9 +10,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/server/ssl" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/server/ssl" + "github.com/prebid/prebid-server/v2/usersync" ) // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go @@ -22,6 +22,7 @@ type UserSyncDeps struct { ExternalUrl string RecaptchaSecret string HostCookieConfig *config.HostCookie + PriorityGroups [][]string } // Struct for parsing json in google's response @@ -58,6 +59,8 @@ func (deps *UserSyncDeps) VerifyRecaptcha(response string) error { func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { optout := r.FormValue("optout") rr := r.FormValue("g-recaptcha-response") + encoder := usersync.Base64Encoder{} + decoder := usersync.Base64Decoder{} if rr == "" { http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), http.StatusMovedPermanently) @@ -73,10 +76,18 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr return } - pc := usersync.ParseCookieFromRequest(r, deps.HostCookieConfig) + // Read Cookie + pc := usersync.ReadCookie(r, decoder, deps.HostCookieConfig) + usersync.SyncHostCookie(r, pc, deps.HostCookieConfig) pc.SetOptOut(optout != "") - pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) + // Write Cookie + encodedCookie, err := encoder.Encode(pc) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + usersync.WriteCookie(w, encodedCookie, deps.HostCookieConfig, false) if optout == "" { http.Redirect(w, r, deps.HostCookieConfig.OptInURL, http.StatusMovedPermanently) diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 872420001ea..fb3fb24d9cc 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" "github.com/buger/jsonparser" "github.com/golang/glog" diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index ec390364849..f3ee3065ff1 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -10,9 +10,10 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -297,7 +298,7 @@ func newHandler(numResponses int) http.HandlerFunc { resp.Responses[i].UUID = strconv.Itoa(i) } - respBytes, _ := json.Marshal(resp) + respBytes, _ := jsonutil.Marshal(resp) w.Write(respBytes) }) } diff --git a/privacy/activity.go b/privacy/activity.go index 7cdef17590c..2309d0204a6 100644 --- a/privacy/activity.go +++ b/privacy/activity.go @@ -1,6 +1,7 @@ package privacy -// Activity defines privileges which can be controlled directly by the publisher or via privacy policies. +// Activity defines Prebid Server actions which can be controlled directly +// by the publisher or via privacy policies. type Activity int const ( @@ -10,7 +11,8 @@ const ( ActivityReportAnalytics ActivityTransmitUserFPD ActivityTransmitPreciseGeo - ActivityTransmitUniqueRequestIds + ActivityTransmitUniqueRequestIDs + ActivityTransmitTIDs ) func (a Activity) String() string { @@ -27,8 +29,10 @@ func (a Activity) String() string { return "transmitUfpd" case ActivityTransmitPreciseGeo: return "transmitPreciseGeo" - case ActivityTransmitUniqueRequestIds: + case ActivityTransmitUniqueRequestIDs: return "transmitUniqueRequestIds" + case ActivityTransmitTIDs: + return "transmitTid" } return "" diff --git a/privacy/activitycontrol.go b/privacy/activitycontrol.go new file mode 100644 index 00000000000..1bb3fc6cdf6 --- /dev/null +++ b/privacy/activitycontrol.go @@ -0,0 +1,120 @@ +package privacy + +import ( + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + +type ActivityResult int + +const ( + ActivityAbstain ActivityResult = iota + ActivityAllow + ActivityDeny +) + +const defaultActivityResult = true + +func NewRequestFromPolicies(p Policies) ActivityRequest { + return ActivityRequest{policies: &p} +} + +func NewRequestFromBidRequest(r openrtb_ext.RequestWrapper) ActivityRequest { + return ActivityRequest{bidRequest: &r} +} + +type ActivityRequest struct { + policies *Policies + bidRequest *openrtb_ext.RequestWrapper +} + +func (r ActivityRequest) IsPolicies() bool { + return r.policies != nil +} + +func (r ActivityRequest) IsBidRequest() bool { + return r.bidRequest != nil +} + +type ActivityControl struct { + plans map[Activity]ActivityPlan +} + +func NewActivityControl(cfg *config.AccountPrivacy) ActivityControl { + ac := ActivityControl{} + + if cfg == nil || cfg.AllowActivities == nil { + return ac + } + + plans := make(map[Activity]ActivityPlan, 8) + plans[ActivitySyncUser] = buildPlan(cfg.AllowActivities.SyncUser) + plans[ActivityFetchBids] = buildPlan(cfg.AllowActivities.FetchBids) + plans[ActivityEnrichUserFPD] = buildPlan(cfg.AllowActivities.EnrichUserFPD) + plans[ActivityReportAnalytics] = buildPlan(cfg.AllowActivities.ReportAnalytics) + plans[ActivityTransmitUserFPD] = buildPlan(cfg.AllowActivities.TransmitUserFPD) + plans[ActivityTransmitPreciseGeo] = buildPlan(cfg.AllowActivities.TransmitPreciseGeo) + plans[ActivityTransmitUniqueRequestIDs] = buildPlan(cfg.AllowActivities.TransmitUniqueRequestIds) + plans[ActivityTransmitTIDs] = buildPlan(cfg.AllowActivities.TransmitTids) + ac.plans = plans + + return ac +} + +func buildPlan(activity config.Activity) ActivityPlan { + return ActivityPlan{ + rules: cfgToRules(activity.Rules), + defaultResult: cfgToDefaultResult(activity.Default), + } +} + +func cfgToRules(rules []config.ActivityRule) []Rule { + var enfRules []Rule + + for _, r := range rules { + result := ActivityDeny + if r.Allow { + result = ActivityAllow + } + + er := ConditionRule{ + result: result, + componentName: r.Condition.ComponentName, + componentType: r.Condition.ComponentType, + } + enfRules = append(enfRules, er) + } + return enfRules +} + +func cfgToDefaultResult(activityDefault *bool) bool { + if activityDefault == nil { + return defaultActivityResult + } + return *activityDefault +} + +func (e ActivityControl) Allow(activity Activity, target Component, request ActivityRequest) bool { + plan, planDefined := e.plans[activity] + + if !planDefined { + return defaultActivityResult + } + + return plan.Evaluate(target, request) +} + +type ActivityPlan struct { + defaultResult bool + rules []Rule +} + +func (p ActivityPlan) Evaluate(target Component, request ActivityRequest) bool { + for _, rule := range p.rules { + result := rule.Evaluate(target, request) + if result == ActivityDeny || result == ActivityAllow { + return result == ActivityAllow + } + } + return p.defaultResult +} diff --git a/privacy/activitycontrol_test.go b/privacy/activitycontrol_test.go new file mode 100644 index 00000000000..b8b06ee8886 --- /dev/null +++ b/privacy/activitycontrol_test.go @@ -0,0 +1,185 @@ +package privacy + +import ( + "testing" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestNewActivityControl(t *testing.T) { + testCases := []struct { + name string + privacyConf config.AccountPrivacy + activityControl ActivityControl + }{ + { + name: "empty", + privacyConf: config.AccountPrivacy{}, + activityControl: ActivityControl{plans: nil}, + }, + { + name: "specified_and_correct", + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + SyncUser: getTestActivityConfig(true), + FetchBids: getTestActivityConfig(true), + EnrichUserFPD: getTestActivityConfig(true), + ReportAnalytics: getTestActivityConfig(true), + TransmitUserFPD: getTestActivityConfig(true), + TransmitPreciseGeo: getTestActivityConfig(false), + TransmitUniqueRequestIds: getTestActivityConfig(true), + TransmitTids: getTestActivityConfig(true), + }, + }, + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivitySyncUser: getTestActivityPlan(ActivityAllow), + ActivityFetchBids: getTestActivityPlan(ActivityAllow), + ActivityEnrichUserFPD: getTestActivityPlan(ActivityAllow), + ActivityReportAnalytics: getTestActivityPlan(ActivityAllow), + ActivityTransmitUserFPD: getTestActivityPlan(ActivityAllow), + ActivityTransmitPreciseGeo: getTestActivityPlan(ActivityDeny), + ActivityTransmitUniqueRequestIDs: getTestActivityPlan(ActivityAllow), + ActivityTransmitTIDs: getTestActivityPlan(ActivityAllow), + }}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualAC := NewActivityControl(&test.privacyConf) + assert.Equal(t, test.activityControl, actualAC) + }) + } +} + +func TestCfgToDefaultResult(t *testing.T) { + testCases := []struct { + name string + activityDefault *bool + expectedResult bool + }{ + { + name: "nil", + activityDefault: nil, + expectedResult: true, + }, + { + name: "true", + activityDefault: ptrutil.ToPtr(true), + expectedResult: true, + }, + { + name: "false", + activityDefault: ptrutil.ToPtr(false), + expectedResult: false, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := cfgToDefaultResult(test.activityDefault) + assert.Equal(t, test.expectedResult, actualResult) + }) + } +} + +func TestActivityControlAllow(t *testing.T) { + testCases := []struct { + name string + activityControl ActivityControl + activity Activity + target Component + activityResult bool + }{ + { + name: "plans_is_nil", + activityControl: ActivityControl{plans: nil}, + activity: ActivityFetchBids, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: true, + }, + { + name: "activity_not_defined", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivitySyncUser: getTestActivityPlan(ActivityAllow)}}, + activity: ActivityFetchBids, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: true, + }, + { + name: "activity_defined_but_not_found_default_returned", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivityFetchBids: getTestActivityPlan(ActivityAllow)}}, + activity: ActivityFetchBids, + target: Component{Type: "bidder", Name: "bidderB"}, + activityResult: true, + }, + { + name: "activity_defined_and_allowed", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivityFetchBids: getTestActivityPlan(ActivityAllow)}}, + activity: ActivityFetchBids, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := test.activityControl.Allow(test.activity, test.target, ActivityRequest{}) + assert.Equal(t, test.activityResult, actualResult) + + }) + } +} + +func TestActivityRequest(t *testing.T) { + t.Run("empty", func(t *testing.T) { + r := ActivityRequest{} + assert.False(t, r.IsPolicies()) + assert.False(t, r.IsBidRequest()) + }) + + t.Run("policies", func(t *testing.T) { + r := NewRequestFromPolicies(Policies{}) + assert.True(t, r.IsPolicies()) + assert.False(t, r.IsBidRequest()) + }) + + t.Run("request", func(t *testing.T) { + r := NewRequestFromBidRequest(openrtb_ext.RequestWrapper{}) + assert.False(t, r.IsPolicies()) + assert.True(t, r.IsBidRequest()) + }) +} + +func getTestActivityConfig(allow bool) config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{"bidderA"}, + ComponentType: []string{"bidder"}, + }, + }, + }, + } +} + +func getTestActivityPlan(result ActivityResult) ActivityPlan { + return ActivityPlan{ + defaultResult: true, + rules: []Rule{ + ConditionRule{ + result: result, + componentName: []string{"bidderA"}, + componentType: []string{"bidder"}, + }, + }, + } +} diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 1d65a272f90..339eb3438fb 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -2,7 +2,7 @@ package ccpa import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the old PolicyWriter interface for CCPA. diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 015f1328f61..e8414a8e2f5 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 7b9c2d1fa7c..056cc99ee1b 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v2/errortypes" ) const ( diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index fbafd8a8a2e..e5412b7d4c7 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -7,9 +7,9 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v2/privacy/gpp" ) // Policy represents the CCPA regulatory information from an OpenRTB bid request. diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 3a1433333c0..e18820b221b 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -8,7 +8,7 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/component.go b/privacy/component.go new file mode 100644 index 00000000000..33767938ab3 --- /dev/null +++ b/privacy/component.go @@ -0,0 +1,25 @@ +package privacy + +import ( + "strings" +) + +const ( + ComponentTypeBidder = "bidder" + ComponentTypeAnalytics = "analytics" + ComponentTypeRealTimeData = "rtd" + ComponentTypeGeneral = "general" +) + +type Component struct { + Type string + Name string +} + +func (c Component) MatchesName(v string) bool { + return strings.EqualFold(c.Name, v) +} + +func (c Component) MatchesType(v string) bool { + return strings.EqualFold(c.Type, v) +} diff --git a/privacy/component_test.go b/privacy/component_test.go new file mode 100644 index 00000000000..9d9d1644f5d --- /dev/null +++ b/privacy/component_test.go @@ -0,0 +1,87 @@ +package privacy + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestComponentMatchesName(t *testing.T) { + testCases := []struct { + name string + given Component + target string + result bool + }{ + { + name: "match", + given: Component{Type: "a", Name: "b"}, + target: "b", + result: true, + }, + { + name: "wrong-field", + given: Component{Type: "a", Name: "b"}, + target: "a", + result: false, + }, + { + name: "different-value", + given: Component{Type: "a", Name: "b"}, + target: "foo", + result: false, + }, + { + name: "different-case", + given: Component{Type: "a", Name: "b"}, + target: "B", + result: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.result, test.given.MatchesName(test.target)) + }) + } +} + +func TestComponentMatchesType(t *testing.T) { + testCases := []struct { + name string + given Component + target string + result bool + }{ + { + name: "match", + given: Component{Type: "a", Name: "b"}, + target: "a", + result: true, + }, + { + name: "wrong-field", + given: Component{Type: "a", Name: "b"}, + target: "b", + result: false, + }, + { + name: "different-value", + given: Component{Type: "a", Name: "b"}, + target: "foo", + result: false, + }, + { + name: "different-case", + given: Component{Type: "a", Name: "b"}, + target: "A", + result: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.result, test.given.MatchesType(test.target)) + }) + } +} diff --git a/privacy/enforcement.go b/privacy/enforcement.go deleted file mode 100644 index 50d42e89435..00000000000 --- a/privacy/enforcement.go +++ /dev/null @@ -1,77 +0,0 @@ -package privacy - -import "github.com/prebid/openrtb/v19/openrtb2" - -// Enforcement represents the privacy policies to enforce for an OpenRTB bid request. -type Enforcement struct { - CCPA bool - COPPA bool - GDPRGeo bool - GDPRID bool - LMT bool -} - -// Any returns true if at least one privacy policy requires enforcement. -func (e Enforcement) Any() bool { - return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT -} - -// Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest) { - e.apply(bidRequest, NewScrubber()) -} - -func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { - if bidRequest != nil && e.Any() { - bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) - bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) - } -} - -func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID { - if e.COPPA || e.GDPRID || e.CCPA || e.LMT { - return ScrubStrategyDeviceIDAll - } - - return ScrubStrategyDeviceIDNone -} - -func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 { - if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV4Lowest8 - } - - return ScrubStrategyIPV4None -} - -func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { - if e.COPPA { - return ScrubStrategyIPV6Lowest32 - } - - if e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV6Lowest16 - } - - return ScrubStrategyIPV6None -} - -func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { - if e.COPPA { - return ScrubStrategyGeoFull - } - - if e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyGeoReducedPrecision - } - - return ScrubStrategyGeoNone -} - -func (e Enforcement) getUserScrubStrategy() ScrubStrategyUser { - if e.COPPA || e.CCPA || e.LMT || e.GDPRID { - return ScrubStrategyUserIDAndDemographic - } - - return ScrubStrategyUserNone -} diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go deleted file mode 100644 index a9d7ca9275a..00000000000 --- a/privacy/enforcement_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package privacy - -import ( - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestAny(t *testing.T) { - testCases := []struct { - enforcement Enforcement - expected bool - description string - }{ - { - description: "All False", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expected: false, - }, - { - description: "All True", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expected: true, - }, - { - description: "Mixed", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expected: true, - }, - } - - for _, test := range testCases { - result := test.enforcement.Any() - assert.Equal(t, test.expected, result, test.description) - } -} - -func TestApply(t *testing.T) { - testCases := []struct { - description string - enforcement Enforcement - expectedDeviceID ScrubStrategyDeviceID - expectedDeviceIPv4 ScrubStrategyIPV4 - expectedDeviceIPv6 ScrubStrategyIPV6 - expectedDeviceGeo ScrubStrategyGeo - expectedUser ScrubStrategyUser - expectedUserGeo ScrubStrategyGeo - }{ - { - description: "All Enforced", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "CCPA Only", - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "COPPA Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "GDPR Only - Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "GDPR Only - ID Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4None, - expectedDeviceIPv6: ScrubStrategyIPV6None, - expectedDeviceGeo: ScrubStrategyGeoNone, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoNone, - }, - { - description: "GDPR Only - Geo Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDNone, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "LMT Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Interactions: COPPA + GDPR Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - } - - for _, test := range testCases { - req := &openrtb2.BidRequest{ - Device: &openrtb2.Device{}, - User: &openrtb2.User{}, - } - replacedDevice := &openrtb2.Device{} - replacedUser := &openrtb2.User{} - - m := &mockScrubber{} - m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() - m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once() - - test.enforcement.apply(req, m) - - m.AssertExpectations(t) - assert.Same(t, replacedDevice, req.Device, "Device") - assert.Same(t, replacedUser, req.User, "User") - } -} - -func TestApplyNoneApplicable(t *testing.T) { - req := &openrtb2.BidRequest{} - - m := &mockScrubber{} - - enforcement := Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - } - enforcement.apply(req, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -func TestApplyNil(t *testing.T) { - m := &mockScrubber{} - - enforcement := Enforcement{} - enforcement.apply(nil, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -type mockScrubber struct { - mock.Mock -} - -func (m *mockScrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - args := m.Called(device, id, ipv4, ipv6, geo) - return args.Get(0).(*openrtb2.Device) -} - -func (m *mockScrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - args := m.Called(user, strategy, geo) - return args.Get(0).(*openrtb2.User) -} diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 00e3558fd40..25bc2bf0ca0 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -2,7 +2,7 @@ package gdpr import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. diff --git a/privacy/gdpr/policy.go b/privacy/gdpr/validate.go similarity index 68% rename from privacy/gdpr/policy.go rename to privacy/gdpr/validate.go index 0464a9ff979..662ad85286e 100644 --- a/privacy/gdpr/policy.go +++ b/privacy/gdpr/validate.go @@ -4,12 +4,6 @@ import ( "github.com/prebid/go-gdpr/vendorconsent" ) -// Policy represents the GDPR regulation for an OpenRTB bid request. -type Policy struct { - Signal string - Consent string -} - // ValidateConsent returns true if the consent string is empty or valid per the IAB TCF spec. func ValidateConsent(consent string) bool { _, err := vendorconsent.ParseString(consent) diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/validate_test.go similarity index 100% rename from privacy/gdpr/policy_test.go rename to privacy/gdpr/validate_test.go diff --git a/privacy/gpp/gpp.go b/privacy/gpp/sid.go similarity index 57% rename from privacy/gpp/gpp.go rename to privacy/gpp/sid.go index 2ebf4b9752a..6e10eac94db 100644 --- a/privacy/gpp/gpp.go +++ b/privacy/gpp/sid.go @@ -5,13 +5,6 @@ import ( gppConstants "github.com/prebid/go-gpp/constants" ) -// Policy represents the GPP privacy string container. -// Currently just a placeholder until more expansive support is made. -type Policy struct { - Consent string - RawSID string // This is the CSV format ("2,6") that the IAB recommends for passing the SID(s) on a query string. -} - // IsSIDInList returns true if the 'sid' value is found in the gppSIDs array. Its logic is used in more than // one place in our codebase, therefore it was decided to make it its own function. func IsSIDInList(gppSIDs []int8, sid gppConstants.SectionID) bool { @@ -23,9 +16,10 @@ func IsSIDInList(gppSIDs []int8, sid gppConstants.SectionID) bool { return false } -// IndexOfSID returns a zero or non-negative integer that represents the position of the 'sid' value in the -// 'gpp.SectionTypes' array. If the 'sid' value is not found, returns -1. This logic is used in -// more than one place in our codebase, therefore it was decided to make it its own function. +// IndexOfSID returns a zero or non-negative integer that represents the position of +// the 'sid' value in the 'gpp.SectionTypes' array. If the 'sid' value is not found, +// returns -1. This logic is used in more than one place in our codebase, therefore +// it was decided to make it its own function. func IndexOfSID(gpp gpplib.GppContainer, sid gppConstants.SectionID) int { for i, id := range gpp.SectionTypes { if id == sid { diff --git a/privacy/gpp/gpp_test.go b/privacy/gpp/sid_test.go similarity index 100% rename from privacy/gpp/gpp_test.go rename to privacy/gpp/sid_test.go diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index 0b308a9ce32..ee08225f8c7 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/iosutil" ) var ( diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 2a679bfbd99..7afaf7843e1 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/prebid-server/v2/util/iosutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/policies.go b/privacy/policies.go index 214696e1d63..16fee92ccce 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -1,16 +1,6 @@ package privacy -import ( - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/privacy/lmt" -) - -// Policies represents the privacy regulations for an OpenRTB bid request. +// Policies contains privacy signals and consent for non-OpenRTB activities. type Policies struct { - CCPA ccpa.Policy - GDPR gdpr.Policy - LMT lmt.Policy - GPP gpp.Policy + GPPSID []int8 } diff --git a/privacy/enforcer.go b/privacy/policyenforcer.go similarity index 94% rename from privacy/enforcer.go rename to privacy/policyenforcer.go index 0d5ecad5309..e70c0d3d190 100644 --- a/privacy/enforcer.go +++ b/privacy/policyenforcer.go @@ -1,5 +1,7 @@ package privacy +// NOTE: Reanme this package. Will eventually replace in its entirety with Activites. + // PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy. type PolicyEnforcer interface { // CanEnforce returns true when policy information is specifically provided by the publisher. diff --git a/privacy/enforcer_test.go b/privacy/policyenforcer_test.go similarity index 100% rename from privacy/enforcer_test.go rename to privacy/policyenforcer_test.go diff --git a/privacy/rule.go b/privacy/rule.go new file mode 100644 index 00000000000..ef370f090ca --- /dev/null +++ b/privacy/rule.go @@ -0,0 +1,5 @@ +package privacy + +type Rule interface { + Evaluate(target Component, request ActivityRequest) ActivityResult +} diff --git a/privacy/rule_condition.go b/privacy/rule_condition.go new file mode 100644 index 00000000000..2fc3450653d --- /dev/null +++ b/privacy/rule_condition.go @@ -0,0 +1,84 @@ +package privacy + +// noClausesDefinedResult represents the default return when there is no matching criteria specified. +const noClausesDefinedResult = true + +type ConditionRule struct { + result ActivityResult + componentName []string + componentType []string + gppSID []int8 +} + +func (r ConditionRule) Evaluate(target Component, request ActivityRequest) ActivityResult { + if matched := evaluateComponentName(target, r.componentName); !matched { + return ActivityAbstain + } + + if matched := evaluateComponentType(target, r.componentType); !matched { + return ActivityAbstain + } + + if matched := evaluateGPPSID(r.gppSID, request); !matched { + return ActivityAbstain + } + + return r.result +} + +func evaluateComponentName(target Component, componentNames []string) bool { + // no clauses are considered a match + if len(componentNames) == 0 { + return noClausesDefinedResult + } + + for _, n := range componentNames { + if target.MatchesName(n) { + return true + } + } + + return false +} + +func evaluateComponentType(target Component, componentTypes []string) bool { + if len(componentTypes) == 0 { + return noClausesDefinedResult + } + + // if there are clauses, at least one needs to match + for _, t := range componentTypes { + if target.MatchesType(t) { + return true + } + } + + return false +} + +func evaluateGPPSID(sid []int8, request ActivityRequest) bool { + if len(sid) == 0 { + return noClausesDefinedResult + } + + for _, x := range getGPPSID(request) { + for _, y := range sid { + if x == y { + return true + } + } + } + return false +} + +func getGPPSID(request ActivityRequest) []int8 { + if request.IsPolicies() { + return request.policies.GPPSID + } + + if request.IsBidRequest() && request.bidRequest.Regs != nil { + return request.bidRequest.Regs.GPPSID + } + + return nil +} diff --git a/privacy/rule_condition_test.go b/privacy/rule_condition_test.go new file mode 100644 index 00000000000..23973bd06d0 --- /dev/null +++ b/privacy/rule_condition_test.go @@ -0,0 +1,278 @@ +package privacy + +import ( + "testing" + + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestComponentEnforcementRuleEvaluate(t *testing.T) { + testCases := []struct { + name string + componentRule ConditionRule + target Component + activityResult ActivityResult + }{ + { + name: "activity_is_allowed", + componentRule: ConditionRule{ + result: ActivityAllow, + componentName: []string{"bidderA"}, + componentType: []string{"bidder"}, + }, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + { + name: "activity_is_not_allowed", + componentRule: ConditionRule{ + result: ActivityDeny, + componentName: []string{"bidderA"}, + componentType: []string{"bidder"}, + }, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: ActivityDeny, + }, + { + name: "abstain_both_clauses_do_not_match", + componentRule: ConditionRule{ + result: ActivityAllow, + componentName: []string{"bidderA"}, + componentType: []string{"bidder"}, + }, + target: Component{Type: "bidder", Name: "bidderB"}, + activityResult: ActivityAbstain, + }, + { + name: "abstain_gppsid", + componentRule: ConditionRule{ + result: ActivityAllow, + gppSID: []int8{1}, + }, + target: Component{Type: "bidder", Name: "bidderB"}, + activityResult: ActivityAbstain, + }, + { + name: "activity_is_not_allowed_componentName_only", + componentRule: ConditionRule{ + result: ActivityAllow, + componentName: []string{"bidderA"}, + }, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + { + name: "activity_is_allowed_componentType_only", + componentRule: ConditionRule{ + result: ActivityAllow, + componentType: []string{"bidder"}, + }, + target: Component{Type: "bidder", Name: "bidderB"}, + activityResult: ActivityAllow, + }, + { + name: "no-conditions-allow", + componentRule: ConditionRule{ + result: ActivityAllow, + }, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + { + name: "no-conditions-deny", + componentRule: ConditionRule{ + result: ActivityDeny, + }, + target: Component{Type: "bidder", Name: "bidderA"}, + activityResult: ActivityDeny, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := test.componentRule.Evaluate(test.target, ActivityRequest{}) + assert.Equal(t, test.activityResult, actualResult) + }) + } +} + +func TestEvaluateGPPSID(t *testing.T) { + testCases := []struct { + name string + sidCondition []int8 + sidRequest []int8 + expected bool + }{ + { + name: "condition-nil-request-nil", + sidCondition: nil, + sidRequest: nil, + expected: true, + }, + { + name: "condition-empty-request-nil", + sidCondition: []int8{}, + sidRequest: nil, + expected: true, + }, + { + name: "condition-nil-request-empty", + sidCondition: nil, + sidRequest: []int8{}, + expected: true, + }, + { + name: "condition-empty-request-empty", + sidCondition: []int8{}, + sidRequest: []int8{}, + expected: true, + }, + { + name: "condition-one-request-nil", + sidCondition: []int8{1}, + sidRequest: nil, + expected: false, + }, + { + name: "condition-many-request-nil", + sidCondition: []int8{1, 2}, + sidRequest: nil, + expected: false, + }, + { + name: "condition-one-request-empty", + sidCondition: []int8{1}, + sidRequest: []int8{}, + expected: false, + }, + { + name: "condition-many-request-empty", + sidCondition: []int8{1, 2}, + sidRequest: []int8{}, + expected: false, + }, + { + name: "condition-one-request-one-match", + sidCondition: []int8{1}, + sidRequest: []int8{1}, + expected: true, + }, + { + name: "condition-one-request-one-nomatch", + sidCondition: []int8{1}, + sidRequest: []int8{2}, + expected: false, + }, + { + name: "condition-one-request-many-match", + sidCondition: []int8{1}, + sidRequest: []int8{1, 2}, + expected: true, + }, + { + name: "condition-one-request-many-nomatch", + sidCondition: []int8{3}, + sidRequest: []int8{1, 2}, + expected: false, + }, + { + name: "condition-nil-request-one", + sidCondition: nil, + sidRequest: []int8{1}, + expected: true, + }, + { + name: "condition-nil-request-many", + sidCondition: nil, + sidRequest: []int8{1, 2}, + expected: true, + }, + { + name: "condition-empty-request-one", + sidCondition: []int8{}, + sidRequest: []int8{1}, + expected: true, + }, + { + name: "condition-empty-request-many", + sidCondition: []int8{}, + sidRequest: []int8{1, 2}, + expected: true, + }, + { + name: "condition-many-request-one-match", + sidCondition: []int8{1, 2}, + sidRequest: []int8{1}, + expected: true, + }, + { + name: "condition-many-request-one-nomatch", + sidCondition: []int8{1, 2}, + sidRequest: []int8{3}, + expected: false, + }, + { + name: "condition-many-request-many-match", + sidCondition: []int8{1, 2}, + sidRequest: []int8{1, 2}, + expected: true, + }, + { + name: "condition-many-request-many-nomatch", + sidCondition: []int8{1, 2}, + sidRequest: []int8{3, 4}, + expected: false, + }, + { + name: "condition-many-request-many-mixed", + sidCondition: []int8{1, 2}, + sidRequest: []int8{2, 3}, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := evaluateGPPSID(test.sidCondition, NewRequestFromPolicies(Policies{GPPSID: test.sidRequest})) + assert.Equal(t, test.expected, actualResult) + }) + } +} + +func TestGetGPPSID(t *testing.T) { + testCases := []struct { + name string + request ActivityRequest + expected []int8 + }{ + { + name: "empty", + request: ActivityRequest{}, + expected: nil, + }, + { + name: "policies", + request: ActivityRequest{policies: &Policies{GPPSID: []int8{1}}}, + expected: []int8{1}, + }, + { + name: "request-regs", + request: ActivityRequest{bidRequest: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: &openrtb2.Regs{GPPSID: []int8{1}}}}}, + expected: []int8{1}, + }, + { + name: "request-regs-nil", + request: ActivityRequest{bidRequest: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: nil}}}, + expected: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := getGPPSID(test.request) + assert.Equal(t, test.expected, actualResult) + }) + } +} diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 428193bcda1..ba1f8a9478b 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -2,196 +2,156 @@ package privacy import ( "encoding/json" - "strings" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "net" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/iputil" ) -// ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. -type ScrubStrategyIPV4 int - -const ( - // ScrubStrategyIPV4None does not remove any part of an IPV4 address. - ScrubStrategyIPV4None ScrubStrategyIPV4 = iota - - // ScrubStrategyIPV4Lowest8 zeroes out the last 8 bits of an IPV4 address. - ScrubStrategyIPV4Lowest8 -) - -// ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address. -type ScrubStrategyIPV6 int - -const ( - // ScrubStrategyIPV6None does not remove any part of an IPV6 address. - ScrubStrategyIPV6None ScrubStrategyIPV6 = iota - - // ScrubStrategyIPV6Lowest16 zeroes out the last 16 bits of an IPV6 address. - ScrubStrategyIPV6Lowest16 - - // ScrubStrategyIPV6Lowest32 zeroes out the last 32 bits of an IPV6 address. - ScrubStrategyIPV6Lowest32 -) - -// ScrubStrategyGeo defines the approach to scrub PII from geographical data. -type ScrubStrategyGeo int - -const ( - // ScrubStrategyGeoNone does not remove any geographical data. - ScrubStrategyGeoNone ScrubStrategyGeo = iota - - // ScrubStrategyGeoFull removes all geographical data. - ScrubStrategyGeoFull - - // ScrubStrategyGeoReducedPrecision anonymizes geographical data with rounding. - ScrubStrategyGeoReducedPrecision -) - -// ScrubStrategyUser defines the approach to scrub PII from user data. -type ScrubStrategyUser int - -const ( - // ScrubStrategyUserNone does not remove non-location data. - ScrubStrategyUserNone ScrubStrategyUser = iota - - // ScrubStrategyUserIDAndDemographic removes the user's buyer id, exchange id year of birth, and gender. - ScrubStrategyUserIDAndDemographic -) - -// ScrubStrategyDeviceID defines the approach to remove hardware id and device id data. -type ScrubStrategyDeviceID int - -const ( - // ScrubStrategyDeviceIDNone does not remove hardware id and device id data. - ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota - - // ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes) - ScrubStrategyDeviceIDAll -) - -// Scrubber removes PII from parts of an OpenRTB request. -type Scrubber interface { - ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device - ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User -} - -type scrubber struct{} - -// NewScrubber returns an OpenRTB scrubber. -func NewScrubber() Scrubber { - return scrubber{} +type IPConf struct { + IPV6 config.IPv6 + IPV4 config.IPv4 } -func (scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - if device == nil { - return nil +func scrubDeviceIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Device != nil { + reqWrapper.Device.DIDMD5 = "" + reqWrapper.Device.DIDSHA1 = "" + reqWrapper.Device.DPIDMD5 = "" + reqWrapper.Device.DPIDSHA1 = "" + reqWrapper.Device.IFA = "" + reqWrapper.Device.MACMD5 = "" + reqWrapper.Device.MACSHA1 = "" } +} - deviceCopy := *device - - switch id { - case ScrubStrategyDeviceIDAll: - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" +func scrubUserIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.Data = nil + reqWrapper.User.ID = "" + reqWrapper.User.BuyerUID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" + reqWrapper.User.Keywords = "" + reqWrapper.User.KwArray = nil } +} - switch ipv4 { - case ScrubStrategyIPV4Lowest8: - deviceCopy.IP = scrubIPV4Lowest8(device.IP) +func scrubUserDemographics(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.BuyerUID = "" + reqWrapper.User.ID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" } +} - switch ipv6 { - case ScrubStrategyIPV6Lowest16: - deviceCopy.IPv6 = scrubIPV6Lowest16Bits(device.IPv6) - case ScrubStrategyIPV6Lowest32: - deviceCopy.IPv6 = scrubIPV6Lowest32Bits(device.IPv6) +func scrubUserExt(reqWrapper *openrtb_ext.RequestWrapper, fieldName string) error { + if reqWrapper.User != nil { + userExt, err := reqWrapper.GetUserExt() + if err != nil { + return err + } + ext := userExt.GetExt() + _, hasField := ext[fieldName] + if hasField { + delete(ext, fieldName) + userExt.SetExt(ext) + } } + return nil +} - switch geo { - case ScrubStrategyGeoFull: - deviceCopy.Geo = scrubGeoFull(device.Geo) - case ScrubStrategyGeoReducedPrecision: - deviceCopy.Geo = scrubGeoPrecision(device.Geo) +func ScrubEIDs(reqWrapper *openrtb_ext.RequestWrapper) error { + //transmitEids removes user.eids and user.ext.eids + if reqWrapper.User != nil { + reqWrapper.User.EIDs = nil } - - return &deviceCopy + return scrubUserExt(reqWrapper, "eids") } -func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - if user == nil { - return nil +func ScrubTID(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Source != nil { + reqWrapper.Source.TID = "" } - - userCopy := *user - - if strategy == ScrubStrategyUserIDAndDemographic { - userCopy.BuyerUID = "" - userCopy.ID = "" - userCopy.Ext = scrubUserExtIDs(userCopy.Ext) - userCopy.Yob = 0 - userCopy.Gender = "" + impWrapper := reqWrapper.GetImp() + for ind, imp := range impWrapper { + impExt := scrubExtIDs(imp.Ext, "tid") + impWrapper[ind].Ext = impExt } + reqWrapper.SetImp(impWrapper) +} - switch geo { - case ScrubStrategyGeoFull: - userCopy.Geo = scrubGeoFull(user.Geo) - case ScrubStrategyGeoReducedPrecision: - userCopy.Geo = scrubGeoPrecision(user.Geo) +func scrubGEO(reqWrapper *openrtb_ext.RequestWrapper) { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = scrubGeoPrecision(reqWrapper.User.Geo) } - return &userCopy + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = scrubGeoPrecision(reqWrapper.Device.Geo) + } } -func scrubIPV4Lowest8(ip string) string { - i := strings.LastIndex(ip, ".") - if i == -1 { - return "" +func scrubGeoFull(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = &openrtb2.Geo{} + } + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = &openrtb2.Geo{} } - return ip[0:i] + ".0" } -func scrubIPV6Lowest16Bits(ip string) string { - ip = removeLowestIPV6Segment(ip) - - if ip != "" { - ip += ":0" +func scrubDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + if reqWrapper.Device != nil { + reqWrapper.Device.IP = scrubIP(reqWrapper.Device.IP, ipConf.IPV4.AnonKeepBits, iputil.IPv4BitSize) + reqWrapper.Device.IPv6 = scrubIP(reqWrapper.Device.IPv6, ipConf.IPV6.AnonKeepBits, iputil.IPv6BitSize) } - - return ip } -func scrubIPV6Lowest32Bits(ip string) string { - ip = removeLowestIPV6Segment(ip) - ip = removeLowestIPV6Segment(ip) +func ScrubDeviceIDsIPsUserDemoExt(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf, fieldName string, scrubFullGeo bool) { + scrubDeviceIDs(reqWrapper) + scrubDeviceIP(reqWrapper, ipConf) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, fieldName) - if ip != "" { - ip += ":0:0" + if scrubFullGeo { + scrubGeoFull(reqWrapper) + } else { + scrubGEO(reqWrapper) } - - return ip } -func removeLowestIPV6Segment(ip string) string { - i := strings.LastIndex(ip, ":") +func ScrubUserFPD(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserIDs(reqWrapper) + scrubUserExt(reqWrapper, "data") + reqWrapper.User.EIDs = nil +} - if i == -1 { - return "" - } +func ScrubGdprID(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, "eids") +} - return ip[0:i] +func ScrubGeoAndDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + scrubDeviceIP(reqWrapper, ipConf) + scrubGEO(reqWrapper) } -func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { - if geo == nil { - return nil +func scrubIP(ip string, ones, bits int) string { + if ip == "" { + return "" } - - return &openrtb2.Geo{} + ipMask := net.CIDRMask(ones, bits) + ipMasked := net.ParseIP(ip).Mask(ipMask) + return ipMasked.String() } func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { @@ -205,25 +165,25 @@ func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { return &geoCopy } -func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage { - if len(userExt) == 0 { - return userExt +func scrubExtIDs(ext json.RawMessage, fieldName string) json.RawMessage { + if len(ext) == 0 { + return ext } var userExtParsed map[string]json.RawMessage - err := json.Unmarshal(userExt, &userExtParsed) + err := jsonutil.Unmarshal(ext, &userExtParsed) if err != nil { - return userExt + return ext } - _, hasEids := userExtParsed["eids"] - if hasEids { - delete(userExtParsed, "eids") - result, err := json.Marshal(userExtParsed) + _, hasField := userExtParsed[fieldName] + if hasField { + delete(userExtParsed, fieldName) + result, err := jsonutil.Marshal(userExtParsed) if err == nil { return result } } - return userExt + return ext } diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 7b4afac247e..1fb88874d43 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -5,476 +5,413 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestScrubDevice(t *testing.T) { - device := &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", +func TestScrubDeviceIDs(t *testing.T) { + testCases := []struct { + name string + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device + }{ + { + name: "all", + deviceIn: &openrtb2.Device{DIDMD5: "MD5", DIDSHA1: "SHA1", DPIDMD5: "MD5", DPIDSHA1: "SHA1", IFA: "IFA", MACMD5: "MD5", MACSHA1: "SHA1"}, + expectedDevice: &openrtb2.Device{DIDMD5: "", DIDSHA1: "", DPIDMD5: "", DPIDSHA1: "", IFA: "", MACMD5: "", MACSHA1: ""}, }, + { + name: "nil", + deviceIn: nil, + expectedDevice: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Device: test.deviceIn}} + scrubDeviceIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedDevice, brw.Device) + }) } +} +func TestScrubUserIDs(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.Device - id ScrubStrategyDeviceID - ipv4 ScrubStrategyIPV4 - ipv6 ScrubStrategyIPV6 - geo ScrubStrategyGeo + name string + userIn *openrtb2.User + expectedUser *openrtb2.User }{ { - description: "All Strategies - None", - expected: device, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "All Strategies - Strictest", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4Lowest8, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoFull, - }, - { - description: "Isolated - ID - All", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv4 - Lowest 8", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4Lowest8, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv6 - Lowest 16", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Lowest16, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv6 - Lowest 32", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Lowest32, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - Geo - Reduced Precision", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Isolated - Geo - Full", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoFull, + name: "all", + userIn: &openrtb2.User{Data: []openrtb2.Data{}, ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M", Keywords: "keywords", KwArray: nil}, + expectedUser: &openrtb2.User{Data: nil, ID: "", BuyerUID: "", Yob: 0, Gender: "", Keywords: "", KwArray: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, }, } - for _, test := range testCases { - result := NewScrubber().ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } } -func TestScrubDeviceNil(t *testing.T) { - result := NewScrubber().ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) - assert.Nil(t, result) -} - -func TestScrubUser(t *testing.T) { - user := &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", +func TestScrubUserDemographics(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "all", + userIn: &openrtb2.User{ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M"}, + expectedUser: &openrtb2.User{ID: "", BuyerUID: "", Yob: 0, Gender: ""}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, }, } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserDemographics(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } +} +func TestScrubUserExt(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.User - scrubUser ScrubStrategyUser - scrubGeo ScrubStrategyGeo + name string + userIn *openrtb2.User + fieldName string + expectedUser *openrtb2.User }{ { - description: "User ID And Demographic & Geo Full", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User ID And Demographic & Geo Reduced", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User ID And Demographic & Geo None", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoNone, - }, - { - description: "User None & Geo Full", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User None & Geo Reduced", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User None & Geo None", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoNone, + name: "nil_user", + userIn: nil, + expectedUser: nil, + }, + { + name: "nil_ext", + userIn: &openrtb2.User{ID: "ID", Ext: nil}, + expectedUser: &openrtb2.User{ID: "ID", Ext: nil}, + }, + { + name: "empty_ext", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + }, + { + name: "ext_with_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "data", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"test":1}`)}, + }, + { + name: "ext_without_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "noData", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, }, } - for _, test := range testCases { - result := NewScrubber().ScrubUser(user, test.scrubUser, test.scrubGeo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserExt(brw, test.fieldName) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } } -func TestScrubUserNil(t *testing.T) { - result := NewScrubber().ScrubUser(nil, ScrubStrategyUserNone, ScrubStrategyGeoNone) - assert.Nil(t, result) +func TestScrubEids(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "eids", + userIn: &openrtb2.User{ID: "ID", EIDs: []openrtb2.EID{}}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil_eids", + userIn: &openrtb2.User{ID: "ID", EIDs: nil}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + ScrubEIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } } -func TestScrubIPV4(t *testing.T) { +func TestScrubTID(t *testing.T) { testCases := []struct { - IP string - cleanedIP string - description string + name string + sourceIn *openrtb2.Source + impIn []openrtb2.Imp + expectedSource *openrtb2.Source + expectedImp []openrtb2.Imp }{ { - IP: "0.0.0.0", - cleanedIP: "0.0.0.0", - description: "Shouldn't do anything for a 0.0.0.0 IP address", + name: "nil", + sourceIn: nil, + expectedSource: nil, }, { - IP: "192.127.111.134", - cleanedIP: "192.127.111.0", - description: "Should remove the lowest 8 bits", + name: "nil_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: nil}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: nil}}, }, { - IP: "192.127.111.0", - cleanedIP: "192.127.111.0", - description: "Shouldn't change anything if the lowest 8 bits are already 0", + name: "empty_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, }, { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "ext_with_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"tid":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"test":1}`)}}, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "ext_without_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, }, } - for _, test := range testCases { - result := scrubIPV4Lowest8(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Source: test.sourceIn, Imp: test.impIn}} + ScrubTID(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedSource, brw.Source) + assert.Equal(t, test.expectedImp, brw.Imp) + }) } } -func TestScrubIPV6Lowest16Bits(t *testing.T) { +func TestScrubGEO(t *testing.T) { testCases := []struct { - IP string - cleanedIP string - description string + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device }{ { - IP: "0:0:0:0", - cleanedIP: "0:0:0:0", - description: "Shouldn't do anything for a 0:0:0:0 IP address", + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, + }, + { + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", - description: "Should remove lowest 16 bits", + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.12}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:0", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", - description: "Shouldn't do anything if the lowest 16 bits are already 0", + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, }, { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.12}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.12}}, }, } - for _, test := range testCases { - result := scrubIPV6Lowest16Bits(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGEO(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) + }) } } -func TestScrubIPV6Lowest32Bits(t *testing.T) { +func TestScrubGeoFull(t *testing.T) { testCases := []struct { - IP string - cleanedIP string - description string + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device }{ { - IP: "0:0:0:0", - cleanedIP: "0:0:0:0", - description: "Shouldn't do anything for a 0:0:0:0 IP address", + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", - description: "Should remove lowest 32 bits", + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0:0", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", - description: "Shouldn't do anything if the lowest 32 bits are already 0", + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, }, - { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: 123.123}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, }, } - for _, test := range testCases { - result := scrubIPV6Lowest32Bits(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGeoFull(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) + }) } } -func TestScrubGeoFull(t *testing.T) { - geo := &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", +func TestScrubIP(t *testing.T) { + testCases := []struct { + IP string + cleanedIP string + bits int + maskBits int + }{ + { + IP: "0:0:0:0:0:0:0:0", + cleanedIP: "::", + bits: 128, + maskBits: 56, + }, + { + IP: "", + cleanedIP: "", + bits: 128, + maskBits: 56, + }, + { + IP: "1111:2222:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:2222:3333:4400::", + bits: 128, + maskBits: 56, + }, + { + IP: "1111:2222:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:2222::", + bits: 128, + maskBits: 34, + }, + { + IP: "1111:0:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:0:3333:4400::", + bits: 128, + maskBits: 56, + }, + { + IP: "1111::6666:7777:8888", + cleanedIP: "1111::", + bits: 128, + maskBits: 56, + }, + { + IP: "2001:1db8:0000:0000:0000:ff00:0042:8329", + cleanedIP: "2001:1db8::ff00:0:0", + bits: 128, + maskBits: 96, + }, + { + IP: "2001:1db8:0000:0000:0000:ff00:0:0", + cleanedIP: "2001:1db8::ff00:0:0", + bits: 128, + maskBits: 96, + }, } - geoExpected := &openrtb2.Geo{ - Lat: 0, - Lon: 0, - Metro: "", - City: "", - ZIP: "", + for _, test := range testCases { + t.Run(test.IP, func(t *testing.T) { + // bits: ipv6 - 128, ipv4 - 32 + result := scrubIP(test.IP, test.maskBits, test.bits) + assert.Equal(t, test.cleanedIP, result) + }) } - - result := scrubGeoFull(geo) - - assert.Equal(t, geoExpected, result) -} - -func TestScrubGeoFullWhenNil(t *testing.T) { - result := scrubGeoFull(nil) - assert.Nil(t, result) } func TestScrubGeoPrecision(t *testing.T) { @@ -549,30 +486,15 @@ func TestScrubUserExtIDs(t *testing.T) { userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, - { - description: "Remove eids Only", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{}`), - }, { description: "Remove eids Only - Empty Array", userExt: json.RawMessage(`{"eids":[]}`), expected: json.RawMessage(`{}`), }, - { - description: "Remove eids Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove eids Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { - result := scrubUserExtIDs(test.userExt) + result := scrubExtIDs(test.userExt, "eids") assert.Equal(t, test.expected, result, test.description) } } diff --git a/router/admin.go b/router/admin.go index 29cdbbe5e23..1be7c8656da 100644 --- a/router/admin.go +++ b/router/admin.go @@ -5,9 +5,9 @@ import ( "net/http/pprof" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/endpoints" + "github.com/prebid/prebid-server/v2/version" ) func Admin(rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go index 39a4341f995..7b94c96b11b 100644 --- a/router/aspects/request_timeout_handler.go +++ b/router/aspects/request_timeout_handler.go @@ -6,8 +6,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" ) func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine metrics.MetricsEngine, requestType metrics.RequestType) httprouter.Handle { diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go index 26e546dcd40..4ece14208e8 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" "github.com/stretchr/testify/assert" ) diff --git a/router/router.go b/router/router.go index 8fc99589c22..d89d1f59ca2 100644 --- a/router/router.go +++ b/router/router.go @@ -10,32 +10,34 @@ import ( "strings" "time" - analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/endpoints/events" - infoEndpoints "github.com/prebid/prebid-server/endpoints/info" - "github.com/prebid/prebid-server/endpoints/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/modules" - "github.com/prebid/prebid-server/modules/moduledeps" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/router/aspects" - "github.com/prebid/prebid-server/server/ssl" - storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/currency" + "github.com/prebid/prebid-server/v2/endpoints" + "github.com/prebid/prebid-server/v2/endpoints/events" + infoEndpoints "github.com/prebid/prebid-server/v2/endpoints/info" + "github.com/prebid/prebid-server/v2/endpoints/openrtb2" + "github.com/prebid/prebid-server/v2/errortypes" + "github.com/prebid/prebid-server/v2/exchange" + "github.com/prebid/prebid-server/v2/experiment/adscert" + "github.com/prebid/prebid-server/v2/floors" + "github.com/prebid/prebid-server/v2/gdpr" + "github.com/prebid/prebid-server/v2/hooks" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/metrics" + metricsConf "github.com/prebid/prebid-server/v2/metrics/config" + "github.com/prebid/prebid-server/v2/modules" + "github.com/prebid/prebid-server/v2/modules/moduledeps" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/pbs" + pbc "github.com/prebid/prebid-server/v2/prebid_cache_client" + "github.com/prebid/prebid-server/v2/router/aspects" + "github.com/prebid/prebid-server/v2/server/ssl" + storedRequestsConf "github.com/prebid/prebid-server/v2/stored_requests/config" + "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/jsonutil" + "github.com/prebid/prebid-server/v2/util/uuidutil" + "github.com/prebid/prebid-server/v2/version" _ "github.com/go-sql-driver/mysql" "github.com/golang/glog" @@ -55,6 +57,10 @@ import ( // This function stores the file contents in memory, and should not be used on large directories. // If the root directory, or any of the files in it, cannot be read, then the program will exit. func NewJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[string]string) httprouter.Handle { + return newJsonDirectoryServer(schemaDirectory, validator, aliases, openrtb_ext.GetAliasBidderToParent()) +} + +func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[string]string, yamlAliases map[openrtb_ext.BidderName]openrtb_ext.BidderName) httprouter.Handle { // Slurp the files into memory first, since they're small and it minimizes request latency. files, err := os.ReadDir(schemaDirectory) if err != nil { @@ -73,6 +79,11 @@ func NewJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.Bidder data[bidder] = json.RawMessage(validator.Schema(bidderName)) } + // Add in any aliases + for aliasName, parentBidder := range yamlAliases { + data[string(aliasName)] = json.RawMessage(validator.Schema(parentBidder)) + } + // Add in any default aliases for aliasName, bidderName := range aliases { bidderData, ok := data[bidderName] @@ -152,6 +163,16 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } + floorFechterHttpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, + MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, + }, + } + if err := checkSupportedUserSyncEndpoints(cfg.BidderInfos); err != nil { return nil, err } @@ -170,7 +191,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R syncerKeys = append(syncerKeys, k) } - moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient} + moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, RateConvertor: rateConvertor} repo, moduleStageNames, err := modules.NewBuilder().Build(cfg.Hooks.Modules, moduleDeps) if err != nil { glog.Fatalf("Failed to init hook modules: %v", err) @@ -182,7 +203,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R // todo(zachbadgett): better shutdown r.Shutdown = shutdown - pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) + analyticsRunner := analyticsBuild.New(&cfg.Analytics) paramsValidator, err := openrtb_ext.NewBidderParamsValidator(schemaDirectory) if err != nil { @@ -190,7 +211,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } activeBidders := exchange.GetActiveBidders(cfg.BidderInfos) - disabledBidders := exchange.GetDisabledBiddersErrorMessages(cfg.BidderInfos) + disabledBidders := exchange.GetDisabledBidderWarningMessages(cfg.BidderInfos) defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) if err := validateDefaultAliases(defaultAliases); err != nil { @@ -214,21 +235,24 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create ads cert signer: %v", err) } + priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) + + tmaxAdjustments := exchange.ProcessTMaxAdjustments(cfg.TmaxAdjustments) planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo) macroReplacer := macros.NewStringIndexBasedReplacer() - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer, priceFloorFetcher) var uuidGenerator uuidutil.UUIDRandomGenerator - openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder) + openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder) + ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient) + videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } @@ -244,7 +268,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(cfg.BidderInfos, defaultAliases)) r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(cfg.BidderInfos, defaultAliases)) r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) - r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle) + r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, analyticsRunner, accounts, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) @@ -257,16 +281,17 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // event endpoint - eventEndpoint := events.NewEventEndpoint(cfg, accounts, pbsAnalytics, r.MetricsEngine) + eventEndpoint := events.NewEventEndpoint(cfg, accounts, analyticsRunner, r.MetricsEngine) r.GET("/event", eventEndpoint) userSyncDeps := &pbs.UserSyncDeps{ HostCookieConfig: &(cfg.HostCookie), ExternalUrl: cfg.ExternalURL, RecaptchaSecret: cfg.RecaptchaSecret, + PriorityGroups: cfg.UserSync.PriorityGroups, } - r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg, syncersByBidder, gdprPermsBuilder, tcf2CfgBuilder, pbsAnalytics, accounts, r.MetricsEngine)) + r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg, syncersByBidder, gdprPermsBuilder, tcf2CfgBuilder, analyticsRunner, accounts, r.MetricsEngine)) r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) r.POST("/optout", userSyncDeps.OptOut) r.GET("/optout", userSyncDeps.OptOut) @@ -347,7 +372,7 @@ func readDefaultRequest(defReqConfig config.DefReqConfig) (map[string]string, [] return aliases, []byte{} } - if err := json.Unmarshal(defReqJSON, defReq); err != nil { + if err := jsonutil.UnmarshalValid(defReqJSON, defReq); err != nil { // we might not have aliases defined, but will atleast show that the JSON file is parsable. glog.Fatalf("error parsing alias json in file %s: %v", defReqConfig.FileSystem.FileName, err) return aliases, []byte{} diff --git a/router/router_test.go b/router/router_test.go index 2b4ff7fd3e7..cc2f077e5e6 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -7,8 +7,9 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -37,14 +38,15 @@ func ensureHasKey(t *testing.T, data map[string]json.RawMessage, key string) { } func TestNewJsonDirectoryServer(t *testing.T) { - alias := map[string]string{"aliastest": "appnexus"} - handler := NewJsonDirectoryServer("../static/bidder-params", &testValidator{}, alias) + defaultAlias := map[string]string{"aliastest": "appnexus"} + yamlAlias := map[openrtb_ext.BidderName]openrtb_ext.BidderName{openrtb_ext.BidderName("alias"): openrtb_ext.BidderName("parentAlias")} + handler := newJsonDirectoryServer("../static/bidder-params", &testValidator{}, defaultAlias, yamlAlias) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/whatever", nil) handler(recorder, request, nil) var data map[string]json.RawMessage - json.Unmarshal(recorder.Body.Bytes(), &data) + jsonutil.UnmarshalValid(recorder.Body.Bytes(), &data) // Make sure that every adapter has a json schema by the same name associated with it. adapterFiles, err := os.ReadDir(adapterDirectory) @@ -59,6 +61,7 @@ func TestNewJsonDirectoryServer(t *testing.T) { } ensureHasKey(t, data, "aliastest") + ensureHasKey(t, data, "alias") } func TestCheckSupportedUserSyncEndpoints(t *testing.T) { diff --git a/schain/schain.go b/schain/schain.go index 6f084a65a2a..a4139a93f5e 100644 --- a/schain/schain.go +++ b/schain/schain.go @@ -5,7 +5,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) // BidderToPrebidSChains organizes the ORTB 2.5 multiple root schain nodes into a map of schain nodes by bidder diff --git a/schain/schain_test.go b/schain/schain_test.go index dbe38d4014b..310608420d9 100644 --- a/schain/schain_test.go +++ b/schain/schain_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/schain/schainwriter.go b/schain/schainwriter.go index 7e2161adb3b..0873e14f199 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -1,10 +1,9 @@ package schain import ( - "encoding/json" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance @@ -70,7 +69,7 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode) } - sourceExt, err := json.Marshal(schain) + sourceExt, err := jsonutil.Marshal(schain) if err == nil { req.Source.Ext = sourceExt } diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index e98c962b4fa..26777306fdf 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -5,7 +5,8 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -197,7 +198,7 @@ func TestSChainWriter(t *testing.T) { var reqExt *openrtb_ext.ExtRequest if tt.giveRequest.Ext != nil { reqExt = &openrtb_ext.ExtRequest{} - err := json.Unmarshal(tt.giveRequest.Ext, reqExt) + err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) if err != nil { t.Error("Unable to unmarshal request.ext") } diff --git a/scripts/check_coverage.sh b/scripts/check_coverage.sh index 0dd6235b96b..63e89297c42 100755 --- a/scripts/check_coverage.sh +++ b/scripts/check_coverage.sh @@ -25,8 +25,7 @@ while IFS= read -r LINE; do if [[ $LINE =~ "%" ]]; then PERCENT=$(echo "$LINE"|cut -d: -f2-|cut -d% -f1|cut -d. -f1|tr -d ' ') if [[ $PERCENT -lt $COV_MIN ]]; then - echo "Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" - exit 1 + echo "WARNING: Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" fi fi done <<< "$OUTPUT" diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 00000000000..d5f33e517e4 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +die() { echo -e "$@" 1>&2 ; exit 1; } + +AUTOFMT=true +while getopts 'f:' OPTION; do + case "$OPTION" in + f) + AUTOFMT="$OPTARG" + ;; + esac +done + +# Build a list of all the top-level directories in the project. +for DIRECTORY in */ ; do + GOGLOB="$GOGLOB ${DIRECTORY%/}" +done +GOGLOB="${GOGLOB/ docs/}" +GOGLOB="${GOGLOB/ vendor/}" + +# Check that there are no formatting issues +GOFMT_LINES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | wc -l | xargs` +if $AUTOFMT; then + # if there are files with formatting issues, they will be automatically corrected using the gofmt -w command + if [[ $GOFMT_LINES -ne 0 ]]; then + FMT_FILES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | xargs` + for FILE in $FMT_FILES; do + echo "Running: gofmt -s -w $FILE" + `gofmt -s -w $FILE` + done + fi +else + test $GOFMT_LINES -eq 0 || die "gofmt needs to be run, ${GOFMT_LINES} files have issues. Below is a list of files to review:\n`gofmt -s -l $GOGLOB`" +fi \ No newline at end of file diff --git a/server/listener.go b/server/listener.go index 43917ac0a05..a10aef6441e 100644 --- a/server/listener.go +++ b/server/listener.go @@ -6,7 +6,7 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" ) // monitorableListener tracks any opened connections in the metrics. diff --git a/server/listener_test.go b/server/listener_test.go index d10a3bdfbf9..c729f2ba55e 100644 --- a/server/listener_test.go +++ b/server/listener_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/server/prometheus.go b/server/prometheus.go index 33114c86a0b..8b841f5151a 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -7,8 +7,8 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { diff --git a/server/server.go b/server/server.go index 31764cc40e9..dd4813adb7f 100644 --- a/server/server.go +++ b/server/server.go @@ -13,14 +13,14 @@ import ( "github.com/NYTimes/gziphandler" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" ) // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down. func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.Handler, metrics *metricsconfig.DetailedMetricsEngine) (err error) { - stopSignals := make(chan os.Signal) + stopSignals := make(chan os.Signal, 1) signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT) // Run the servers. Fan any process-stopper signals out to each server for graceful shutdowns. diff --git a/server/server_test.go b/server/server_test.go index 23b45656d7d..03a2fc911b5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v2/config" + metricsconfig "github.com/prebid/prebid-server/v2/metrics/config" "github.com/stretchr/testify/assert" ) @@ -192,7 +192,6 @@ func TestListen(t *testing.T) { Port: 8000, UnixSocketEnable: false, UnixSocketName: "prebid_socket", - EnableGzip: false, } ) diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 902db6b362b..cbbdedb2193 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,4 +1,7 @@ endpoint: "https://ssc.33across.com/api/v1/s2s" +# This bidder does not operate globally. Please consider setting "disabled: true" in European datacenters. +geoscope: + - "!EEA" maintainer: email: "headerbidding@33across.com" gvlVendorID: 58 @@ -10,4 +13,4 @@ capabilities: userSync: iframe: url: "https://ssc-cms.33across.com/ps/?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" - userMacro: "33XUSERID33X" \ No newline at end of file + userMacro: "33XUSERID33X" diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 2310f346c25..2ad892785ce 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1,18 +1,4 @@ -endpoint: "https://adx.adform.net/adx/openrtb" -maintainer: - email: "scope.sspp@adform.com" -gvlVendorID: 50 -capabilities: - app: - mediaTypes: - - banner - - native - - video - site: - mediaTypes: - - banner - - native - - video +aliasOf: adf userSync: redirect: url: "https://cm.adform.net/cookie?redirect_url={{.RedirectURL}}" diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index 864ca71a088..1afbdc8d590 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -12,6 +12,6 @@ capabilities: - video userSync: redirect: - url: "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "{UID}" -endpointCompression: "GZIP" \ No newline at end of file +endpointCompression: "GZIP" diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 5001309593e..3932a7a75e6 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -1,4 +1,4 @@ -endpoint: "https://{{.Host}}" +endpoint: "https://{{.Host}}.adocean.pl" maintainer: email: "aoteam@gemius.com" gvlVendorID: 328 diff --git a/static/bidder-info/adquery.yaml b/static/bidder-info/adquery.yaml new file mode 100644 index 00000000000..035bd6fc1cb --- /dev/null +++ b/static/bidder-info/adquery.yaml @@ -0,0 +1,13 @@ +endpoint: https://bidder2.adquery.io/prebid/bid +maintainer: + email: prebid@adquery.io +gvlVendorID: 902 +capabilities: + site: + mediaTypes: + - banner +userSync: + iframe: + url: https://api.adquery.io/storage?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa_consent={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: $UID + \ No newline at end of file diff --git a/static/bidder-info/adsinteractive.yaml b/static/bidder-info/adsinteractive.yaml index d430e63b2f0..095184725b8 100644 --- a/static/bidder-info/adsinteractive.yaml +++ b/static/bidder-info/adsinteractive.yaml @@ -12,6 +12,6 @@ capabilities: - banner userSync: redirect: - url: "https://bid.adsinteractive.com/getuid?{{.RedirectURL}}" + url: "https://sync.adsinteractive.com/getuid?{{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}" userMacro: "$AUID" diff --git a/static/bidder-info/adsyield.yaml b/static/bidder-info/adsyield.yaml index dadd9cc3d13..2be1395b851 100644 --- a/static/bidder-info/adsyield.yaml +++ b/static/bidder-info/adsyield.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.open-adsyield.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" diff --git a/static/bidder-info/alkimi.yaml b/static/bidder-info/alkimi.yaml new file mode 100644 index 00000000000..c27e2ca47e1 --- /dev/null +++ b/static/bidder-info/alkimi.yaml @@ -0,0 +1,15 @@ +endpoint: https://exchange.alkimi-onboarding.com/server/bid +maintainer: + email: support@alkimi.org +gvlVendorID: 1169 +capabilities: + app: + mediaTypes: + - banner + - video + - audio + site: + mediaTypes: + - banner + - video + - audio diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml index 3ede0d0875f..7ab3cadf304 100644 --- a/static/bidder-info/amx.yaml +++ b/static/bidder-info/amx.yaml @@ -14,7 +14,12 @@ capabilities: - video - native userSync: + iframe: + url: "https://prebid.a-mo.net/isyn?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&s=pbs&cb={{.RedirectURL}}" + userMacro: "$UID" redirect: - url: "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" - userMacro: "" - + url: "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&s=pbs&cb={{.RedirectURL}}" + userMacro: "$UID" +endpointCompression: GZIP +openrtb: + gpp-supported: true diff --git a/static/bidder-info/appstock.yaml b/static/bidder-info/appstock.yaml new file mode 100644 index 00000000000..73b54aa7144 --- /dev/null +++ b/static/bidder-info/appstock.yaml @@ -0,0 +1,2 @@ +endpoint: "http://ads-pbs.pre.vr-tb.com/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/axis.yaml b/static/bidder-info/axis.yaml index 494091a16df..fe04b6015fb 100644 --- a/static/bidder-info/axis.yaml +++ b/static/bidder-info/axis.yaml @@ -1,4 +1,4 @@ -endpoint: "http://prebid.axis-marketplace.com/{{.AccountID}}?token={{.SourceId}}" +endpoint: "http://prebid.axis-marketplace.com/pbserver" maintainer: email: "help@axis-marketplace.com" capabilities: diff --git a/static/bidder-info/bematterfull.yaml b/static/bidder-info/bematterfull.yaml new file mode 100644 index 00000000000..f4b48ee700e --- /dev/null +++ b/static/bidder-info/bematterfull.yaml @@ -0,0 +1,19 @@ +endpoint: "http://prebid-srv.mtflll-system.live/?pid={{.SourceId}}&host={{.Host}}" +maintainer: + email: "adops@bematterfull.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # mtflll-system.live supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/bliink.yaml b/static/bidder-info/bliink.yaml index bcdacc42f07..4650e9f8441 100644 --- a/static/bidder-info/bliink.yaml +++ b/static/bidder-info/bliink.yaml @@ -15,6 +15,9 @@ capabilities: - video - native userSync: + iframe: + url: "https://tag.bliink.io/usersync.html?gdpr={{.GDPR}}&gdprConsent={{.GDPRConsent}}&uspConsent={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" redirect: url: "https://cookiesync.api.bliink.io/getuid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" userMacro: "$UID" diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index 2992d0fbc7b..3d3f2ca6e22 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -3,6 +3,10 @@ maintainer: email: dev@brightmountainmedia.com modifyingVastXmlAllowed: false capabilities: + app: + mediaTypes: + - banner + - video site: mediaTypes: - banner diff --git a/static/bidder-info/boldwin.yaml b/static/bidder-info/boldwin.yaml index 39c1caf34f8..f35d08ca197 100644 --- a/static/bidder-info/boldwin.yaml +++ b/static/bidder-info/boldwin.yaml @@ -13,3 +13,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://sync.videowalldirect.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml deleted file mode 100644 index 196344e9f25..00000000000 --- a/static/bidder-info/brightroll.yaml +++ /dev/null @@ -1,17 +0,0 @@ -endpoint: "http://east-bid.ybp.yahoo.com/bid/appnexuspbs" -maintainer: - email: "dsp-supply-prebid@verizonmedia.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - redirect: - url: "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" - userMacro: "$UID" diff --git a/static/bidder-info/cadent_aperture_mx.yaml b/static/bidder-info/cadent_aperture_mx.yaml new file mode 100644 index 00000000000..b5c72bd9a9d --- /dev/null +++ b/static/bidder-info/cadent_aperture_mx.yaml @@ -0,0 +1,17 @@ +endpoint: "https://hb.emxdgt.com" +maintainer: + email: "contactaperturemx@cadent.tv" +gvlVendorID: 183 +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video +userSync: + iframe: + url: "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index cc290149be2..0f240e87942 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -2,10 +2,13 @@ endpoint: "https://e.serverbid.com/api/v2" maintainer: email: "prebid@consumable.com" gvlVendorID: 591 +endpointCompression: gzip capabilities: app: mediaTypes: - banner + - video + - audio site: mediaTypes: - banner diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index 2135544d509..7ffd2761fde 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -13,6 +13,6 @@ capabilities: - video userSync: redirect: - url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl={{.RedirectURL}}" + url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&rurl={{.RedirectURL}}" userMacro: "" # epsilon appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/copper6.yaml b/static/bidder-info/copper6.yaml index 213cbe3624d..b4e259ef08a 100644 --- a/static/bidder-info/copper6.yaml +++ b/static/bidder-info/copper6.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.app.copper6.com/pbs/ortb" maintainer: email: "info@copper6.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # Copper6 ssp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml index 418b0cabc1e..1f8d6e8d089 100644 --- a/static/bidder-info/datablocks.yaml +++ b/static/bidder-info/datablocks.yaml @@ -1,4 +1,4 @@ -endpoint: "http://{{.Host}}/openrtb2?sid={{.SourceId}}" +endpoint: "http://pbserver.dblks.net/openrtb2?sid={{.SourceId}}" maintainer: email: "prebid@datablocks.net" capabilities: diff --git a/static/bidder-info/definemedia.yaml b/static/bidder-info/definemedia.yaml index d79c3835a58..a7be6fb9d23 100644 --- a/static/bidder-info/definemedia.yaml +++ b/static/bidder-info/definemedia.yaml @@ -1,6 +1,6 @@ endpoint: "https://rtb.conative.network/openrtb2/auction" maintainer: - email: "d.joest@definemedia.de" + email: "development@definemedia.de" gvlVendorID: 440 # GDPR vendor list ID capabilities: diff --git a/static/bidder-info/dxkulture.yaml b/static/bidder-info/dxkulture.yaml new file mode 100644 index 00000000000..d6f03044fda --- /dev/null +++ b/static/bidder-info/dxkulture.yaml @@ -0,0 +1,16 @@ +endpoint: "https://ads.dxkulture.com/pbs" +maintainer: + email: "devops@dxkulture.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + redirect: + url: "https://ads.dxkulture.com/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/edge226.yaml b/static/bidder-info/edge226.yaml new file mode 100644 index 00000000000..c4fbdefc29b --- /dev/null +++ b/static/bidder-info/edge226.yaml @@ -0,0 +1,16 @@ +endpoint: "http://ssp.dauup.com/pserver" +maintainer: + email: "audit@edge226.com" +gvlVendorID: 1202 +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/embimedia.yaml b/static/bidder-info/embimedia.yaml new file mode 100644 index 00000000000..9cfa1546bbe --- /dev/null +++ b/static/bidder-info/embimedia.yaml @@ -0,0 +1,2 @@ +endpoint: "http://ads-pbs.bidder-embi.media/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/emx_digital.yaml b/static/bidder-info/emx_digital.yaml index 0d8aa7f3a6d..b5c72bd9a9d 100644 --- a/static/bidder-info/emx_digital.yaml +++ b/static/bidder-info/emx_digital.yaml @@ -1,6 +1,6 @@ endpoint: "https://hb.emxdgt.com" maintainer: - email: "adops@emxdigital.com" + email: "contactaperturemx@cadent.tv" gvlVendorID: 183 capabilities: site: diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml deleted file mode 100644 index 8218040c605..00000000000 --- a/static/bidder-info/engagebdr.yaml +++ /dev/null @@ -1,14 +0,0 @@ -endpoint: "http://dsp.bnmla.com/hb" -maintainer: - email: "tech@engagebdr.com" -capabilities: - app: - mediaTypes: - - banner - - video - - native -userSync: - iframe: - url: "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "${UUID}" - diff --git a/static/bidder-info/epsilon.yaml b/static/bidder-info/epsilon.yaml index 856d82a6f3f..828bac6cfc4 100644 --- a/static/bidder-info/epsilon.yaml +++ b/static/bidder-info/epsilon.yaml @@ -1,16 +1,4 @@ -endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25" -maintainer: - email: "PublisherIntegration@epsilon.com" -gvlVendorID: 24 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: conversant userSync: redirect: url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl={{.RedirectURL}}" diff --git a/static/bidder-info/evtech.yaml b/static/bidder-info/evtech.yaml index 4cc75cd3da6..59134e04523 100644 --- a/static/bidder-info/evtech.yaml +++ b/static/bidder-info/evtech.yaml @@ -1,16 +1,9 @@ endpoint: "http://ads-pbs.direct.e-volution.ai/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" +userSync: + iframe: + url: https://tracker.direct.e-volution.ai/sync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: "{PLL_USER_ID}" + redirect: + url: https://tracker.direct.e-volution.ai/sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: "{PLL_USER_ID}" diff --git a/static/bidder-info/finative.yaml b/static/bidder-info/finative.yaml new file mode 100644 index 00000000000..e1136c3220d --- /dev/null +++ b/static/bidder-info/finative.yaml @@ -0,0 +1,2 @@ +endpoint: "https://b.finative.cloud/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" \ No newline at end of file diff --git a/static/bidder-info/freewheel-ssp.yaml b/static/bidder-info/freewheel-ssp.yaml index f013445a820..43c1ca166a2 100644 --- a/static/bidder-info/freewheel-ssp.yaml +++ b/static/bidder-info/freewheel-ssp.yaml @@ -1,16 +1,5 @@ -endpoint: "https://ads.stickyadstv.com/openrtb/dsp" -maintainer: - email: prebid-maintainer@freewheel.com -gvlVendorID: 285 -modifyingVastXmlAllowed: true -capabilities: - app: - mediaTypes: - - video - site: - mediaTypes: - - video +aliasOf: freewheelssp userSync: iframe: - url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "{viewerid}" + url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" + userMacro: "{viewerid}" \ No newline at end of file diff --git a/static/bidder-info/freewheelssp.yaml b/static/bidder-info/freewheelssp.yaml index 5cef0de13b3..8c9286cbbc0 100644 --- a/static/bidder-info/freewheelssp.yaml +++ b/static/bidder-info/freewheelssp.yaml @@ -12,5 +12,5 @@ capabilities: - video userSync: iframe: - url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "{viewerid}" \ No newline at end of file diff --git a/static/bidder-info/gothamads.yaml b/static/bidder-info/gothamads.yaml new file mode 100644 index 00000000000..c9f4e66b484 --- /dev/null +++ b/static/bidder-info/gothamads.yaml @@ -0,0 +1,14 @@ +endpoint: "http://us-e-node1.gothamads.com/?pass={{.AccountID}}" +maintainer: + email: "support@gothamads.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/greedygame.yaml b/static/bidder-info/greedygame.yaml new file mode 100644 index 00000000000..330a7debd9d --- /dev/null +++ b/static/bidder-info/greedygame.yaml @@ -0,0 +1,2 @@ +endpoint: "http://ads-pbs.rtb-greedygame.com/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/huaweiads.yaml b/static/bidder-info/huaweiads.yaml index 8fe35b4e012..fc36ff953bd 100644 --- a/static/bidder-info/huaweiads.yaml +++ b/static/bidder-info/huaweiads.yaml @@ -1,7 +1,7 @@ endpoint: "https://acd.op.hicloud.com/ppsadx/getResult" disabled: true maintainer: - email: hwads@huawei.com + email: prebid@huawei.com gvlVendorID: 856 modifyingVastXmlAllowed: true capabilities: diff --git a/static/bidder-info/iionads.yaml b/static/bidder-info/iionads.yaml index da2f494bb30..1dc154a358d 100644 --- a/static/bidder-info/iionads.yaml +++ b/static/bidder-info/iionads.yaml @@ -1,16 +1,2 @@ endpoint: "http://ads-pbs.iionads.com/openrtb/{{.PublisherID}}?host={{.Host}}" -maintainer: - email: "engineering@project-limelight.com" -capabilities: - app: - mediaTypes: - - banner - - video - - audio - - native - site: - mediaTypes: - - banner - - video - - audio - - native +aliasOf: "limelightDigital" \ No newline at end of file diff --git a/static/bidder-info/imds.yaml b/static/bidder-info/imds.yaml index 491a5bd0ac6..135b67fc1f9 100644 --- a/static/bidder-info/imds.yaml +++ b/static/bidder-info/imds.yaml @@ -1,4 +1,8 @@ endpoint: "https://pbs.technoratimedia.com/openrtb/bids/{{.AccountID}}?src={{.SourceId}}&adapter=imds" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA + - CAN maintainer: email: "eng-demand@imds.tv" capabilities: diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index d14356cde0e..4cd30d7c6b2 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -7,14 +7,16 @@ capabilities: mediaTypes: - banner - video + - audio - native site: mediaTypes: - banner - video + - audio - native userSync: redirect: - url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "{PUB_USER_ID}" endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/indicue.yaml b/static/bidder-info/indicue.yaml new file mode 100644 index 00000000000..1353424dfed --- /dev/null +++ b/static/bidder-info/indicue.yaml @@ -0,0 +1,9 @@ +aliasOf: adtelligent +endpoint: "http://ghb.console.indicue.com/pbs/ortb" +maintainer: + email: "ops@indicue.com" +userSync: + # Indicue ssp supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index ad3562de64b..72bdd21d974 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,6 +1,6 @@ endpoint: "https://api.w.inmobi.com/showad/openrtb/bidder/prebid" maintainer: - email: "technology-irv@inmobi.com" + email: "prebid-support@inmobi.com" gvlVendorID: 333 capabilities: app: diff --git a/static/bidder-info/iqx.yaml b/static/bidder-info/iqx.yaml new file mode 100644 index 00000000000..3569c3accf8 --- /dev/null +++ b/static/bidder-info/iqx.yaml @@ -0,0 +1,19 @@ +endpoint: "http://rtb.iqzone.com?pid={{.SourceId}}&host={{.Host}}&pbs=1" +maintainer: + email: "adops@iqzone.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # IQX supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect diff --git a/static/bidder-info/iqzone.yaml b/static/bidder-info/iqzone.yaml index 3465cfabcb0..620ea01e43c 100644 --- a/static/bidder-info/iqzone.yaml +++ b/static/bidder-info/iqzone.yaml @@ -1,6 +1,6 @@ endpoint: "http://smartssp-us-east.iqzone.com/pserver" maintainer: - email: "smartssp@iqzone.com" + email: "adops@iqzone.com" capabilities: site: mediaTypes: diff --git a/static/bidder-info/janet.yaml b/static/bidder-info/janet.yaml index 0a3abb53af4..97f44a2bd56 100644 --- a/static/bidder-info/janet.yaml +++ b/static/bidder-info/janet.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.bidder.jmgads.com/pbs/ortb" maintainer: email: "info@thejmg.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # JANet supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/lemmadigital.yaml b/static/bidder-info/lemmadigital.yaml new file mode 100644 index 00000000000..535c91ffa77 --- /dev/null +++ b/static/bidder-info/lemmadigital.yaml @@ -0,0 +1,14 @@ +endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}" +maintainer: + email: support@lemmatechnologies.com +endpointCompression: gzip +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/liftoff.yaml b/static/bidder-info/liftoff.yaml new file mode 100644 index 00000000000..43470bc7020 --- /dev/null +++ b/static/bidder-info/liftoff.yaml @@ -0,0 +1,12 @@ +endpoint: "https://rtb.ads.vungle.com/bid/t/c770f32" +maintainer: + email: vxssp@liftoff.io +endpointCompression: gzip +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video diff --git a/static/bidder-info/lm_kiviads.yaml b/static/bidder-info/lm_kiviads.yaml new file mode 100644 index 00000000000..dcb252a45d9 --- /dev/null +++ b/static/bidder-info/lm_kiviads.yaml @@ -0,0 +1,19 @@ +endpoint: "http://pbs.kiviads.live/?pid={{.SourceId}}&host={{.Host}}" +maintainer: + email: "prebid@kiviads.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # lmkiviads supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/lockerdome.yaml b/static/bidder-info/lockerdome.yaml index cfefb2f995b..530c54eaaff 100644 --- a/static/bidder-info/lockerdome.yaml +++ b/static/bidder-info/lockerdome.yaml @@ -1,4 +1,7 @@ endpoint: "https://lockerdome.com/ladbid/prebidserver/openrtb2" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA maintainer: email: "bidding@lockerdome.com" capabilities: diff --git a/static/bidder-info/lunamedia.yaml b/static/bidder-info/lunamedia.yaml index ef34143eb40..6f450382c0e 100644 --- a/static/bidder-info/lunamedia.yaml +++ b/static/bidder-info/lunamedia.yaml @@ -1,6 +1,8 @@ endpoint: "http://rtb.lunamedia.live/?pid={{.PublisherID}}" +geoscope: + - USA maintainer: - email: "josh@lunamedia.io" + email: "cs@lunamedia.io" capabilities: site: mediaTypes: diff --git a/static/bidder-info/mabidder.yaml b/static/bidder-info/mabidder.yaml new file mode 100644 index 00000000000..3f03fd87854 --- /dev/null +++ b/static/bidder-info/mabidder.yaml @@ -0,0 +1,11 @@ +endpoint: "https://prebid.ecdrsvc.com/pbs" +maintainer: + email: lmprebidadapter@loblaw.ca +endpointCompression: gzip +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-info/magnite.yaml b/static/bidder-info/magnite.yaml new file mode 100644 index 00000000000..109c20d03bf --- /dev/null +++ b/static/bidder-info/magnite.yaml @@ -0,0 +1 @@ +aliasOf: "rubicon" \ No newline at end of file diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index b78b21e16ea..be19cc6c68a 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -12,5 +12,5 @@ capabilities: - banner - video - native -userSync: - key: "adnxs" +# userSync: +# key: "adnxs" diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml index e1474f775fc..1d6b323c3a6 100644 --- a/static/bidder-info/mobilefuse.yaml +++ b/static/bidder-info/mobilefuse.yaml @@ -1,4 +1,8 @@ endpoint: "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA + - CAN maintainer: email: prebid@mobilefuse.com gvlVendorID: 909 @@ -8,4 +12,4 @@ capabilities: - banner - video - native -endpointCompression: "GZIP" \ No newline at end of file +endpointCompression: "GZIP" diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml deleted file mode 100644 index 639c5450d2e..00000000000 --- a/static/bidder-info/nanointeractive.yaml +++ /dev/null @@ -1,15 +0,0 @@ -endpoint: "https://ad.audiencemanager.de/hbs" -maintainer: - email: "development@nanointeractive.com" -gvlVendorID: 72 -capabilities: - app: - mediaTypes: - - banner - site: - mediaTypes: - - banner -userSync: - redirect: - url: "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" - userMacro: "$UID" diff --git a/static/bidder-info/ninthdecimal.yaml b/static/bidder-info/ninthdecimal.yaml deleted file mode 100755 index 8a4d7b8d299..00000000000 --- a/static/bidder-info/ninthdecimal.yaml +++ /dev/null @@ -1,18 +0,0 @@ -endpoint: "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}" -maintainer: - email: "abudig@ninthdecimal.com" -capabilities: - site: - mediaTypes: - - banner - - video - - app: - mediaTypes: - - banner - - video -userSync: - iframe: - url: "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect={{.RedirectURL}}" - userMacro: "$UID" - diff --git a/static/bidder-info/kubient.yaml b/static/bidder-info/oms.yaml similarity index 51% rename from static/bidder-info/kubient.yaml rename to static/bidder-info/oms.yaml index 15c2708bcb3..8bb9299d6e9 100644 --- a/static/bidder-info/kubient.yaml +++ b/static/bidder-info/oms.yaml @@ -1,13 +1,11 @@ -endpoint: "https://kssp.kbntx.ch/prebid" +endpoint: "http://rt.marphezis.com/pbs" maintainer: - email: "prebid@kubient.com" + email: "prebid@onlinemediasolutions.com" capabilities: app: mediaTypes: - banner - - video site: mediaTypes: - banner - - video diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml index 3677921ee5c..bc52faffe1b 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -1,4 +1,6 @@ endpoint: "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}" +openrtb: + version: 2.6 maintainer: email: devops@onetag.com gvlVendorID: 241 diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index b7891af3195..c42fb91de44 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -6,3 +6,12 @@ capabilities: app: mediaTypes: - banner + site: + mediaTypes: + - banner + - native +userSync: + supportCors: true + redirect: + url: "https://orbidder.otto.de/pbs-usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&redirect={{.RedirectURL}}" + userMacro: "[ODN_ID]" \ No newline at end of file diff --git a/static/bidder-info/ownadx.yaml b/static/bidder-info/ownadx.yaml new file mode 100644 index 00000000000..37567db1144 --- /dev/null +++ b/static/bidder-info/ownadx.yaml @@ -0,0 +1,12 @@ +endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.AccountID}}/{{.ZoneID}}?token={{.SourceId}}" +maintainer: + email: prebid-team@techbravo.com +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/pgam.yaml b/static/bidder-info/pgam.yaml index 0c9b2f008b6..e0bf4388d4b 100644 --- a/static/bidder-info/pgam.yaml +++ b/static/bidder-info/pgam.yaml @@ -1,15 +1,7 @@ +aliasOf: adtelligent endpoint: "http://ghb.pgamssp.com/pbs/ortb" maintainer: email: "ppatel@pgammedia.com" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # PGAM ssp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/pgamssp.yaml b/static/bidder-info/pgamssp.yaml new file mode 100644 index 00000000000..a762d8f15d4 --- /dev/null +++ b/static/bidder-info/pgamssp.yaml @@ -0,0 +1,19 @@ +endpoint: "http://us-east.pgammedia.com/pserver" +maintainer: + email: "info@pgammedia.com" +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://cs.pgammedia.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/pwbid.yaml b/static/bidder-info/pwbid.yaml index ac6872738b4..a172ff39c83 100644 --- a/static/bidder-info/pwbid.yaml +++ b/static/bidder-info/pwbid.yaml @@ -1,4 +1,4 @@ -endpoint: "https://bid.pubwise.io/prebid" +endpoint: "https://bidder.east2.pubwise.io/bid/pubwisedirect" maintainer: email: info@pubwise.io gvlVendorID: 842 @@ -17,4 +17,4 @@ userSync: # PubWise supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. supports: - - redirect \ No newline at end of file + - redirect diff --git a/static/bidder-info/quantumdex.yaml b/static/bidder-info/quantumdex.yaml index 590737ac28b..fae2a987dd7 100644 --- a/static/bidder-info/quantumdex.yaml +++ b/static/bidder-info/quantumdex.yaml @@ -1,16 +1,4 @@ -endpoint: "http://useast.quantumdex.io/auction/pbs" -maintainer: - email: "support@apacdex.com" -modifyingVastXmlAllowed: false -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: "apacdex" userSync: iframe: url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} diff --git a/static/bidder-info/relevantdigital.yaml b/static/bidder-info/relevantdigital.yaml new file mode 100644 index 00000000000..cc873f2804a --- /dev/null +++ b/static/bidder-info/relevantdigital.yaml @@ -0,0 +1,17 @@ +endpoint: "https://{{.Host}}.relevant-digital.com/openrtb2/auction" +maintainer: + email: "support@relevant-digital.com" +gvlVendorID: 1100 +capabilities: + app: + mediaTypes: + - banner + - video + - native + - audio + site: + mediaTypes: + - banner + - video + - native + - audio diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml deleted file mode 100644 index 529eae12628..00000000000 --- a/static/bidder-info/rhythmone.yaml +++ /dev/null @@ -1,17 +0,0 @@ -endpoint: "http://tag.1rx.io/rmp" -maintainer: - email: "support@rhythmone.com" -gvlVendorID: 36 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - redirect: - url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "[RX_UUID]" \ No newline at end of file diff --git a/static/bidder-info/rise.yaml b/static/bidder-info/rise.yaml index d429e8780a7..72e56c334c8 100644 --- a/static/bidder-info/rise.yaml +++ b/static/bidder-info/rise.yaml @@ -2,7 +2,7 @@ endpoint: "https://pbs.yellowblue.io/pbs" maintainer: email: rise-prog-dev@risecodes.com gvlVendorID: 1043 -modifyingVastXmlAllowed: false +modifyingVastXmlAllowed: true capabilities: app: mediaTypes: diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index 6a116448f22..b80cc0ff4f8 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,11 +1,13 @@ endpoint: "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids" maintainer: email: "prebid@rtbhouse.com" +endpointCompression: gzip gvlVendorID: 16 capabilities: site: mediaTypes: - banner + - native userSync: # rtbhouse supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index b3de838c9fd..c3943058511 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,5 +1,15 @@ -endpoint: "http://exapi-us-east.rubiconproject.com/a/api/exchange.json" +# Contact global-support@magnite.com to ask about enabling a connection to the Magnite (formerly Rubicon) exchange. +# We have the following regional endpoint domains: exapi-us-east, exapi-us-west, exapi-apac, exapi-eu +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://REGION.rubiconproject.com/a/api/exchange" +endpointCompression: GZIP +geoscope: + - global disabled: true +xapi: + username: GET_FROM_MAGNITE + password: GET_FROM_MAGNITE + tracker: SAME_AS_USERNAME maintainer: email: "header-bidding@rubiconproject.com" gvlVendorID: 52 @@ -15,7 +25,6 @@ capabilities: - video - native userSync: - # rubicon supports user syncing, but requires configuration by the host. contact this - # bidder directly at the email address in this file to ask about enabling user sync. + # rubicon supports user syncing, but requires configuration. Please contact global-support@magnite.com. supports: - redirect diff --git a/static/bidder-info/applogy.yaml b/static/bidder-info/screencore.yaml similarity index 60% rename from static/bidder-info/applogy.yaml rename to static/bidder-info/screencore.yaml index 62a70a17f28..67777a13782 100644 --- a/static/bidder-info/applogy.yaml +++ b/static/bidder-info/screencore.yaml @@ -1,6 +1,6 @@ -endpoint: "http://rtb.applogy.com/v1/prebid" +endpoint: 'http://h1.screencore.io/?kp={{.AccountID}}&kn={{.SourceId}}' maintainer: - email: work@applogy.com + email: 'connect@screencore.io' capabilities: app: mediaTypes: diff --git a/static/bidder-info/seedingAlliance.yaml b/static/bidder-info/seedingAlliance.yaml index dee9fcd6026..9dcb7701b34 100644 --- a/static/bidder-info/seedingAlliance.yaml +++ b/static/bidder-info/seedingAlliance.yaml @@ -1,4 +1,4 @@ -endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp=pbs" +endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp={{.AccountID}}" maintainer: email: tech@seeding-alliance.de gvlVendorID: 371 diff --git a/static/bidder-info/silverpush.yaml b/static/bidder-info/silverpush.yaml new file mode 100644 index 00000000000..19da68a2e8c --- /dev/null +++ b/static/bidder-info/silverpush.yaml @@ -0,0 +1,12 @@ +endpoint: "https://prebid.chocolateplatform.co/bidder/?identifier=prebidchoc" +maintainer: + email: "prebid@silverpush.co" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index 4eb5f17c17c..2b1b5c3fe17 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -15,7 +15,9 @@ capabilities: - video - native userSync: + # This bidder does not sync when GDPR is in-scope. Please consider removing the usersync + # block when deploying to European datacenters redirect: - url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}" + url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" userMacro: "$UID" diff --git a/static/bidder-info/smartx.yaml b/static/bidder-info/smartx.yaml new file mode 100644 index 00000000000..9a387ecfbd2 --- /dev/null +++ b/static/bidder-info/smartx.yaml @@ -0,0 +1,12 @@ +endpoint: "https://bid.smartclip.net/bid/1005" +maintainer: + email: "bidding@smartclip.tv" +gvlVendorID: 115 +modifyingVastXmlAllowed: false +capabilities: + site: + mediaTypes: + - video + app: + mediaTypes: + - video diff --git a/static/bidder-info/smartyads.yaml b/static/bidder-info/smartyads.yaml index b36983bc7b1..21b8a0a11d6 100644 --- a/static/bidder-info/smartyads.yaml +++ b/static/bidder-info/smartyads.yaml @@ -1,6 +1,7 @@ endpoint: "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}" maintainer: email: "support@smartyads.com" +gvlVendorID: 534 capabilities: app: mediaTypes: diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index 06dadcf9a05..62d74152b0b 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -17,6 +17,5 @@ userSync: url: "https://ap.lijit.com/pixel?redir={{.RedirectURL}}" userMacro: "$UID" iframe: - url: "https://ap.lijit.com/beacon/prebid-server/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + url: "https://ap.lijit.com/beacon/prebid-server/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&url={{.RedirectURL}}" userMacro: "$UID" - \ No newline at end of file diff --git a/static/bidder-info/sovrnXsp.yaml b/static/bidder-info/sovrnXsp.yaml new file mode 100644 index 00000000000..3cce11a551c --- /dev/null +++ b/static/bidder-info/sovrnXsp.yaml @@ -0,0 +1,12 @@ +endpoint: "http://xsp.lijit.com/json/rtb/prebid/server" +maintainer: + email: "sovrnoss@sovrn.com" +endpointCompression: gzip +gvlVendorID: 13 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/streamkey.yaml b/static/bidder-info/streamkey.yaml index 9e5d05abaec..20510ceac69 100644 --- a/static/bidder-info/streamkey.yaml +++ b/static/bidder-info/streamkey.yaml @@ -1,3 +1,4 @@ +aliasOf: adtelligent endpoint: "http://ghb.hb.streamkey.net/pbs/ortb" maintainer: email: "contact@streamkey.tv" diff --git a/static/bidder-info/stroeerCore.yaml b/static/bidder-info/stroeerCore.yaml index 32c78590bb8..9c0904508ac 100644 --- a/static/bidder-info/stroeerCore.yaml +++ b/static/bidder-info/stroeerCore.yaml @@ -7,9 +7,11 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner + - video userSync: # for both user syncs, stroeerCore appends the user id to end of the redirect url and does not utilize a macro iframe: diff --git a/static/bidder-info/suntContent.yaml b/static/bidder-info/suntContent.yaml index e46cc4086e0..bb5eb2ee977 100644 --- a/static/bidder-info/suntContent.yaml +++ b/static/bidder-info/suntContent.yaml @@ -1,13 +1,7 @@ -endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp=pbs" -maintainer: - email: tech@seeding-alliance.de +endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" gvlVendorID: 1097 -capabilities: - site: - mediaTypes: - - native - - banner userSync: redirect: url: "https://dmp.suntcontent.se/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 2a796ae839f..ccbba0700a4 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -1,15 +1,3 @@ # DEPRECATED: Use imds bidder instead +aliasOf: imds endpoint: "https://pbs.technoratimedia.com/openrtb/bids/{{.AccountID}}?src={{.SourceId}}&adapter=synacormedia" -maintainer: - email: "eng-demand@imds.tv" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - key: "imds" diff --git a/static/bidder-info/taboola.yaml b/static/bidder-info/taboola.yaml index 348165a992c..436f746959a 100644 --- a/static/bidder-info/taboola.yaml +++ b/static/bidder-info/taboola.yaml @@ -1,4 +1,4 @@ -endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction/{{.GvlID}}/{{.PublisherID}}" +endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction?exchange={{.GvlID}}&publisher={{.PublisherID}}" maintainer: email: ps-team@taboola.com gvlVendorID: 42 diff --git a/static/bidder-info/teads.yaml b/static/bidder-info/teads.yaml new file mode 100644 index 00000000000..307b631efb4 --- /dev/null +++ b/static/bidder-info/teads.yaml @@ -0,0 +1,9 @@ +endpoint: "https://psrv.teads.tv/prebid-server/bid-request" +maintainer: + email: "innov-ssp@teads.tv" +gvlVendorID: 132 +capabilities: + app: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/tpmn.yaml b/static/bidder-info/tpmn.yaml new file mode 100644 index 00000000000..8d88c451da6 --- /dev/null +++ b/static/bidder-info/tpmn.yaml @@ -0,0 +1,20 @@ +endpoint: "https://gat.tpmn.io/ortb/pbs_bidder" +maintainer: + email: "prebid@tpmn.io" +modifyingVastXmlAllowed: true +endpointCompression: GZIP +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://gat.tpmn.io/sync/redirect?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 45811c0c868..605bcc71e6e 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -16,9 +16,11 @@ userSync: # Contact this bidder directly at the email address above to ask about enabling user sync. # iframe: - url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: $UID redirect: - url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: "$UID" -endpointCompression: "GZIP" \ No newline at end of file +endpointCompression: "GZIP" +openrtb: + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index 85ebd1a52cc..ff93b544c4c 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -16,8 +16,10 @@ userSync: # Contact this bidder directly at the email address above to ask about enabling user sync. # iframe: - url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: $UID redirect: - url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "$UID" \ No newline at end of file + url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "$UID" +openrtb: + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml index 5e407782e6a..1c60ac34c2f 100644 --- a/static/bidder-info/trustx.yaml +++ b/static/bidder-info/trustx.yaml @@ -1,17 +1,9 @@ -endpoint: "https://grid.bidswitch.net/sp_bid?sp=trustx" +aliasOf: grid maintainer: email: "grid-tech@themediagrid.com" gvlVendorID: 686 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: redirect: url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" - userMacro: "${BSW_UUID}" \ No newline at end of file + userMacro: "${BSW_UUID}" + \ No newline at end of file diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml index 9e6e583967c..fae2a987dd7 100644 --- a/static/bidder-info/valueimpression.yaml +++ b/static/bidder-info/valueimpression.yaml @@ -1,20 +1,8 @@ -endpoint: "http://useast.quantumdex.io/auction/pbs" -maintainer: - email: "support@apacdex.com" -modifyingVastXmlAllowed: false -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: "apacdex" userSync: iframe: url: https://sync.quantumdex.io/usersync/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} userMacro: "[UID]" redirect: url: "https://sync.quantumdex.io/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" - userMacro: "[UID]" \ No newline at end of file + userMacro: "[UID]" diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml index c5e1e8ef02c..8d14cb961d8 100644 --- a/static/bidder-info/viewdeos.yaml +++ b/static/bidder-info/viewdeos.yaml @@ -1,16 +1,8 @@ +aliasOf: adtelligent endpoint: "http://ghb.sync.viewdeos.com/pbs/ortb" maintainer: email: "contact@viewdeos.com" gvlVendorID: 924 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # viewdeos supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/vox.yaml b/static/bidder-info/vox.yaml new file mode 100644 index 00000000000..e2b5c902be6 --- /dev/null +++ b/static/bidder-info/vox.yaml @@ -0,0 +1,19 @@ +endpoint: "https://ssp.hybrid.ai/prebid/server/v1/auction" +maintainer: + email: prebid@hybrid.ai +endpointCompression: gzip +gvlVendorID: 206 +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video + app: + mediaTypes: + - banner + - video +userSync: + redirect: + url: "https://ssp.hybrid.ai/prebid/server/v1/userSync?consent={{.GDPRConsent}}&redirect={{.RedirectURL}}" + userMacro: $UID \ No newline at end of file diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index 0983d546fe9..1ce005cd223 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -7,13 +7,18 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: - # vrtcal supports user syncing, but requires configuration by the host. contact this - # bidder directly at the email address in this file to ask about enabling user sync. - supports: - - redirect - + iframe: + url: "https://usync.vrtcal.com/i?ssp=1804&synctype=iframe&us_privacy={{.USPrivacy}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&surl={{.RedirectURL}}" + userMacro: "$$VRTCALUSER$$" + redirect: + url: "https://usync.vrtcal.com/i?ssp=1804&synctype=redirect&us_privacy={{.USPrivacy}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&surl={{.RedirectURL}}" + userMacro: "$$VRTCALUSER$$" +openrtb: + gpp-supported: true diff --git a/static/bidder-info/xtrmqb.yaml b/static/bidder-info/xtrmqb.yaml new file mode 100644 index 00000000000..b7a068c3889 --- /dev/null +++ b/static/bidder-info/xtrmqb.yaml @@ -0,0 +1,2 @@ +endpoint: "http://ads-pbs.ortb.tech/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/yahooAds.yaml b/static/bidder-info/yahooAds.yaml new file mode 100644 index 00000000000..a2581387152 --- /dev/null +++ b/static/bidder-info/yahooAds.yaml @@ -0,0 +1,18 @@ +endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" +maintainer: + email: "hb-fe-tech@yahooinc.com" +gvlVendorID: 25 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + # yahooAds supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/yahooAdvertising.yaml b/static/bidder-info/yahooAdvertising.yaml index 3798adf7ca2..f9f34fbfbd0 100644 --- a/static/bidder-info/yahooAdvertising.yaml +++ b/static/bidder-info/yahooAdvertising.yaml @@ -1,16 +1,4 @@ -endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" -maintainer: - email: "hb-fe-tech@yahooinc.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: yahooAds userSync: # yahooAdvertising supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/yahoossp.yaml b/static/bidder-info/yahoossp.yaml index 8c23c01e181..81695624dfa 100644 --- a/static/bidder-info/yahoossp.yaml +++ b/static/bidder-info/yahoossp.yaml @@ -1,16 +1,6 @@ -endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" +aliasOf: yahooAds maintainer: email: "hb-fe-tech@oath.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # yahoossp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/yeahmobi.yaml b/static/bidder-info/yeahmobi.yaml index ae41464f6fa..975bf044c5b 100644 --- a/static/bidder-info/yeahmobi.yaml +++ b/static/bidder-info/yeahmobi.yaml @@ -1,6 +1,6 @@ endpoint: "https://{{.Host}}/prebid/bid" maintainer: - email: "junping.zhao@yeahmobi.com" + email: "developer@yeahmobi.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/zeta_global_ssp.yaml b/static/bidder-info/zeta_global_ssp.yaml index 3cc2ecb5297..479f024c1b7 100644 --- a/static/bidder-info/zeta_global_ssp.yaml +++ b/static/bidder-info/zeta_global_ssp.yaml @@ -1,4 +1,4 @@ -endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA&shortname=GET_SHORTNAME_FROM_ZETA +endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA endpointCompression: gzip maintainer: email: DL-Zeta-SSP@zetaglobal.com @@ -15,6 +15,6 @@ capabilities: - video userSync: redirect: - url: https://ssp.disqus.com/redirectuser?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}&partner=GET_SHORTNAME_FROM_ZETA + url: https://ssp.disqus.com/redirectuser?sid=GET_SID_FROM_ZETA&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} userMacro: 'BUYERUID' diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json deleted file mode 100644 index e112f122e49..00000000000 --- a/static/bidder-params/adform.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adform Adapter Params", - "description": "A schema which validates params accepted by the adf adapter", - "type": "object", - "properties": { - "mid": { - "type": ["integer", "string"], - "pattern": "^\\d+$", - "description": "An ID which identifies the placement selling the impression" - }, - "inv": { - "type": ["integer"], - "description": "An ID which identifies the Adform inventory source id" - }, - "mname": { - "type": ["string"], - "description": "A Name which identifies the placement selling the impression" - }, - "priceType": { - "type": ["string"], - "description": "gross or net. Default is net.", - "pattern": "gross|net" - } - }, - "anyOf":[ - { - "required": ["mid"] - }, { - "required": ["inv", "mname"] - } - ] -} diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json index ff975501edb..f7ab32d2f48 100644 --- a/static/bidder-params/adnuntius.json +++ b/static/bidder-params/adnuntius.json @@ -9,6 +9,10 @@ "type": "string", "description": "Placement ID" }, + "maxDeals": { + "type": "integer", + "description": "Specify how many deals that you want to return from the auction." + }, "network": { "type": "string", "description": "Network if required" @@ -16,6 +20,10 @@ "noCookies": { "type": "boolean", "description": "Disable cookies being set by the ad server." + }, + "bidType": { + "type": "string", + "description": "Allows you to specify Net or Gross bids." } }, diff --git a/static/bidder-params/adocean.json b/static/bidder-params/adocean.json index 7530c64784c..a3c5cbc95d4 100644 --- a/static/bidder-params/adocean.json +++ b/static/bidder-params/adocean.json @@ -6,9 +6,14 @@ "properties": { "emiter": { "type": "string", - "description": "AdOcean emiter", + "description": "Deprecated, use emitterPrefix instead. AdOcean emiter", "pattern": ".+" }, + "emitterPrefix": { + "type": "string", + "description": "AdOcean emitter prefix", + "pattern": "^[\\w\\-]+$" + }, "masterId": { "type": "string", "description": "Master's id", @@ -20,5 +25,8 @@ "pattern": "^adocean[\\w.]+$" } }, - "required": ["emiter", "masterId", "slaveId"] + "oneOf": [ + { "required": ["emiter", "masterId", "slaveId"] }, + { "required": ["emitterPrefix", "masterId", "slaveId"] } + ] } diff --git a/static/bidder-params/adquery.json b/static/bidder-params/adquery.json new file mode 100644 index 00000000000..f2394b5e249 --- /dev/null +++ b/static/bidder-params/adquery.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adquery Adapter Params", + "description": "A schema which validates params accepted by the Adquery adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 35, + "maxLength": 45, + "description": "Placement ID" + }, + "type": { + "type": "string", + "minLength": 1, + "description": "Bid type" + } + }, + + "required": ["placementId", "type"] +} diff --git a/static/bidder-params/adsyield.json b/static/bidder-params/adsyield.json deleted file mode 100644 index c7c5308890f..00000000000 --- a/static/bidder-params/adsyield.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adsyield Adapter Params", - "description": "A schema which validates params accepted by the Adsyield adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/alkimi.json b/static/bidder-params/alkimi.json new file mode 100644 index 00000000000..93e09022469 --- /dev/null +++ b/static/bidder-params/alkimi.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Alkimi Adapter Params", + "description": "A schema which validates params accepted by the Alkimi adapter", + "type": "object", + + "properties": { + "token": { + "type": "string", + "description": "Publisher Token provided by Alkimi" + }, + "bidFloor": { + "type": "number", + "description": "The minimum CPM price in USD", + "minimum": 0 + }, + "instl": { + "type": "number", + "description": "Set to 1 if using interstitial (default: 0)" + }, + "exp": { + "type": "number", + "description": "Advisory as to the number of seconds that may elapse between the auction and the actual impression" + } + }, + + "required": ["token"] +} diff --git a/static/bidder-params/bematterfull.json b/static/bidder-params/bematterfull.json new file mode 100644 index 00000000000..db3a505050c --- /dev/null +++ b/static/bidder-params/bematterfull.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bematterfull Adapter Params", + "description": "A schema which validates params accepted by the bematterfull adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "bematterfull environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Uniq placement ID", + "minLength": 1 + } + }, + "required": [ + "env", + "pid" + ] +} diff --git a/static/bidder-params/brightroll.json b/static/bidder-params/brightroll.json deleted file mode 100644 index 48e48d8a36d..00000000000 --- a/static/bidder-params/brightroll.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Brightroll Adapter Params", - "description": "A schema which validates params accepted by the Brightroll adapter", - "type": "object", - "properties": { - "publisher": { - "type": "string", - "description": "Publisher Name to use." - } - }, - "required": ["publisher"] -} diff --git a/static/bidder-params/cadent_aperture_mx.json b/static/bidder-params/cadent_aperture_mx.json new file mode 100644 index 00000000000..4e3ff51fc1b --- /dev/null +++ b/static/bidder-params/cadent_aperture_mx.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Cadent Aperture MX Adapter Params", + "description": "A schema which validates params accepted by the Cadent Aperture MX adapter", + "type": "object", + "properties": { + "tagid" : { + "type": "string", + "description": "The id of an inventory target" + }, + "bidfloor": { + "type": "string", + "description": "The minimum price acceptable for a bid" + } + }, + + "required": ["tagid"] + } diff --git a/static/bidder-params/consumable.json b/static/bidder-params/consumable.json index b1db53568b9..7709ab09d37 100644 --- a/static/bidder-params/consumable.json +++ b/static/bidder-params/consumable.json @@ -24,7 +24,15 @@ "type": "string", "description": "The unit name from Consumable (expected to be a valid CSS class name)", "pattern": "^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$" + }, + "placementId": { + "type": "string", + "description": "The placementID from Consumable, Required for non-site requests", + "pattern": "^[a-zA-Z0-9]+$" } }, - "required": ["siteId", "networkId","unitId"] + "oneOf": [ + {"required": ["siteId", "networkId","unitId"] }, + {"required": ["placementId"] } + ] } diff --git a/static/bidder-params/copper6.json b/static/bidder-params/copper6.json deleted file mode 100644 index fa4050f6c84..00000000000 --- a/static/bidder-params/copper6.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Copper6 Adapter Params", - "description": "A schema which validates params accepted by the Copper6 ssp adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} diff --git a/static/bidder-params/datablocks.json b/static/bidder-params/datablocks.json index 2abb986786b..59b7f94706c 100644 --- a/static/bidder-params/datablocks.json +++ b/static/bidder-params/datablocks.json @@ -9,11 +9,7 @@ "type": "integer", "minimum": 1, "description": "Website Source Id" - }, - "host": { - "type": "string", - "description": "Network Host to request from" } }, - "required": ["host", "sourceId"] + "required": ["sourceId"] } diff --git a/static/bidder-params/dxkulture.json b/static/bidder-params/dxkulture.json new file mode 100644 index 00000000000..858eefd22e4 --- /dev/null +++ b/static/bidder-params/dxkulture.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DXKulture Adapter Params", + "description": "A schema which validates params accepted by the DXKulture adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "The publisher id" + }, + "placementId": { + "type": "string", + "description": "The placement id" + } + }, + "required": [ + "publisherId", + "placementId" + ] +} diff --git a/static/bidder-params/edge226.json b/static/bidder-params/edge226.json new file mode 100644 index 00000000000..95b0fbdabb8 --- /dev/null +++ b/static/bidder-params/edge226.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Edge226 Adapter Params", + "description": "A schema which validates params accepted by the Edge226 adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file diff --git a/static/bidder-params/emx_digital.json b/static/bidder-params/emx_digital.json index f7516f57e63..42c243a9f4f 100644 --- a/static/bidder-params/emx_digital.json +++ b/static/bidder-params/emx_digital.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "EMX Digital Adapter Params", - "description": "A schema which validates params accepted by the EMX Digital adapter", + "description": "A schema which validates params accepted by the Cadent Aperture MX adapter", "type": "object", "properties": { "tagid" : { diff --git a/static/bidder-params/epsilon.json b/static/bidder-params/epsilon.json deleted file mode 100644 index a33a68325e4..00000000000 --- a/static/bidder-params/epsilon.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Epsilon Adapter Params", - "description": "A schema which validates params accepted by the Epsilon adapter.", - "type": "object", - "properties": { - "site_id": { - "type": "string", - "description": "An Epsilon specific ID which identifies the site." - }, - "secure": { - "type": "integer", - "description": "Override http/https context on ad markup." - }, - "bidfloor" : { - "type": "number", - "description": "Minimum bid price that will be considered." - }, - "tag_id": { - "type": "string", - "description": "Identifies specific ad placement." - }, - "position": { - "type": "integer", - "description": "Ad position on screen." - }, - "mimes": { - "type": "array", - "description": "Array of content MIME types. For videos only.", - "items": { - "type": "string" - } - }, - "maxduration": { - "type": "integer", - "description": "Maximum duration in seconds. For videos only." - }, - "api": { - "type": "array", - "description": "Array of supported API frameworks. For videos only.", - "items": { - "type": "integer" - } - }, - "protocols": { - "type": "array", - "description": "Array of supported video protocols. For videos only.", - "items": { - "type": "integer" - } - } - }, - "required": ["site_id"] -} \ No newline at end of file diff --git a/static/bidder-params/evtech.json b/static/bidder-params/evtech.json deleted file mode 100644 index 5e78b7de171..00000000000 --- a/static/bidder-params/evtech.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Evolution Technologies Adapter Params", - "description": "A schema which validates params accepted by the Evolution Technologies adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/freewheelssp.json b/static/bidder-params/freewheelssp.json index 6f93501a3e1..ced3c28daf3 100644 --- a/static/bidder-params/freewheelssp.json +++ b/static/bidder-params/freewheelssp.json @@ -6,7 +6,7 @@ "properties": { "zoneId": { - "type": "integer", + "type": ["integer", "string"], "description": "Zone ID" } }, diff --git a/static/bidder-params/suntContent.json b/static/bidder-params/gothamads.json similarity index 63% rename from static/bidder-params/suntContent.json rename to static/bidder-params/gothamads.json index e85ad3f5de5..030d43eb311 100644 --- a/static/bidder-params/suntContent.json +++ b/static/bidder-params/gothamads.json @@ -1,16 +1,16 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "SUNT Content Adapter Params", - "description": "A schema which validates params accepted by the SUNT Content adapter", + "title": "Gothamads Adapter Params", + "description": "A schema which validates params accepted by the Gothamads adapter", "type": "object", "properties": { - "adUnitId": { + "accountId": { "type": "string", - "description": "Ad Unit ID", + "description": "Account id", "minLength": 1 } }, "required": [ - "adUnitId" + "accountId" ] } \ No newline at end of file diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 95f05e7d517..c9972713f87 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -20,6 +20,10 @@ "slot": { "type": "integer", "description": "A slot id used to identify a slot placement mapped to a GumGum zone or publisher" + }, + "product": { + "type": "string", + "description": "Product param that allow support for Desktop Skins - display and video" } }, "anyOf": [ @@ -34,4 +38,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/static/bidder-params/iionads.json b/static/bidder-params/iionads.json deleted file mode 100644 index b9196445acc..00000000000 --- a/static/bidder-params/iionads.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Iion Adapter Params", - "description": "A schema which validates params accepted by the Iion adapter", - "type": "object", - - "properties": { - "host": { - "type": "string", - "description": "Ad network's RTB host", - "format": "hostname", - "pattern": "^.+\\..+$" - }, - "publisherId": { - "type": ["integer", "string"], - "description": "Publisher ID", - "minimum": 1, - "pattern": "^[1-9][0-9]*$" - } - }, - - "required": ["host", "publisherId"] -} diff --git a/static/bidder-params/iqx.json b/static/bidder-params/iqx.json new file mode 100644 index 00000000000..447c92beeb8 --- /dev/null +++ b/static/bidder-params/iqx.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "IQX Adapter Params", + "description": "A schema which validates params accepted by the iqzonex adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "IQX environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Unique placement ID", + "minLength": 1 + } + }, + "required": [ + "env", + "pid" + ] +} diff --git a/static/bidder-params/ix.json b/static/bidder-params/ix.json index a7a5cb7308a..172690cca32 100644 --- a/static/bidder-params/ix.json +++ b/static/bidder-params/ix.json @@ -27,6 +27,11 @@ "minItems": 2, "maxItems": 2, "description": "An array of two integer containing the dimension" + }, + "sid": { + "type": "string", + "minLength": 1, + "description": "Slot ID" } }, "oneOf": [ diff --git a/static/bidder-params/janet.json b/static/bidder-params/janet.json deleted file mode 100644 index 38c2c2eb044..00000000000 --- a/static/bidder-params/janet.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "JANet Adapter Params", - "description": "A schema which validates params accepted by the JANet adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} diff --git a/static/bidder-params/kubient.json b/static/bidder-params/kubient.json deleted file mode 100644 index 9b975289a7b..00000000000 --- a/static/bidder-params/kubient.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Kubient Adapter Params", - "description": "A schema which validates params accepted by the Kubient adapter", - "type": "object", - "properties": { - "zoneid": { - "type": "string", - "description": "Zone ID identifies Kubient placement ID.", - "minLength": 1 - } - } -} diff --git a/static/bidder-params/lemmadigital.json b/static/bidder-params/lemmadigital.json new file mode 100644 index 00000000000..be4a89edd7a --- /dev/null +++ b/static/bidder-params/lemmadigital.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Lemma Adapter Params", + "description": "A schema which validates params accepted by the Lemma adapter", + "type": "object", + + "properties": { + "pid": { + "type": "integer", + "description": "Publisher ID" + }, + "aid": { + "type": "integer", + "description": "Ad ID" + } + }, + + "required": ["pid", "aid"] +} \ No newline at end of file diff --git a/static/bidder-params/liftoff.json b/static/bidder-params/liftoff.json new file mode 100644 index 00000000000..32aa7f89a53 --- /dev/null +++ b/static/bidder-params/liftoff.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Liftoff Adapter Params", + "description": "A schema which validates params accepted by the Liftoff adapter", + "type": "object", + "properties": { + "app_store_id": { + "type": "string", + "minLength": 1, + "description": "Pub App Store ID" + }, + "placement_reference_id": { + "type": "string", + "minLength": 1, + "description": "Placement Reference ID" + } + }, + "required": [ + "app_store_id", + "placement_reference_id" + ] +} \ No newline at end of file diff --git a/static/bidder-params/lm_kiviads.json b/static/bidder-params/lm_kiviads.json new file mode 100644 index 00000000000..901a1a8e81c --- /dev/null +++ b/static/bidder-params/lm_kiviads.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Lm_Kiviads Adapter Params", + "description": "A schema which validates params accepted by the lmkiviads adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "kivi environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Unique placement ID", + "minLength": 1 + } + }, + "required": [ + "env", + "pid" + ] +} diff --git a/static/bidder-params/applogy.json b/static/bidder-params/mabidder.json similarity index 57% rename from static/bidder-params/applogy.json rename to static/bidder-params/mabidder.json index 2650640c115..f005e88e33d 100644 --- a/static/bidder-params/applogy.json +++ b/static/bidder-params/mabidder.json @@ -1,13 +1,15 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Applogy Adapter Params", - "description": "A schema which validates params accepted by the Applogy adapter", + "title": "Mabidder Adapter Params", + "description": "A schema which validates params accepted by the Mabidder adapter", "type": "object", + "properties": { - "token": { + "ppid": { "type": "string", - "description": "Applogy token" + "description": "Publisher Placement ID" } }, - "required": ["token"] -} + + "required": ["ppid"] +} \ No newline at end of file diff --git a/static/bidder-params/nanointeractive.json b/static/bidder-params/nanointeractive.json deleted file mode 100644 index aacd3154a92..00000000000 --- a/static/bidder-params/nanointeractive.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "NanoInteractive Adapter Params", - "description": "A schema which validates params accepted by the NanoInteractive adapter", - "type": "object", - "properties": { - "pid": { - "type": "string", - "description": "Placement id" - }, - "nq": { - "type": "array", - "items": { - "type": "string" - }, - "description": "search queries" - }, - "category": { - "type": "string", - "description": "IAB Category" - }, - "subId": { - "type": "string", - "description": "any segment value provided by publisher" - }, - "ref" : { - "type": "string", - "description": "referer" - } - }, - "required": ["pid"] -} diff --git a/static/bidder-params/ninthdecimal.json b/static/bidder-params/ninthdecimal.json deleted file mode 100755 index f230361d77e..00000000000 --- a/static/bidder-params/ninthdecimal.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "NinthDecimal Adapter Params", - "description": "A schema which validates params accepted by the NinthDecimal adapter", - "type": "object", - "properties": { - "pubid": { - "type": "string", - "description": "An id used to identify NinthDecimal publisher.", - "minLength": 8 - }, - "placement": { - "type": "string", - "description": "A placement created on adserver." - } - }, - "required": ["pubid"] -} diff --git a/static/bidder-params/engagebdr.json b/static/bidder-params/oms.json similarity index 51% rename from static/bidder-params/engagebdr.json rename to static/bidder-params/oms.json index 4f987004045..f33286d10d9 100644 --- a/static/bidder-params/engagebdr.json +++ b/static/bidder-params/oms.json @@ -1,14 +1,14 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "EngageBDR Adapter Params", - "description": "A schema which validates params accepted by the EngageBDR adapter", + "title": "Online Media Solutions Adapter Params", + "description": "A schema which validates params accepted by the OMS adapter", "type": "object", "properties": { - "sspid": { + "pid": { "type": "string", - "description": "SSPID parameter", - "pattern": "^[0-9]+$" + "description": "An id used to identify OMS publisher.", + "minLength": 5 } }, - "required": ["sspid"] + "required": ["pid"] } diff --git a/static/bidder-params/ownadx.json b/static/bidder-params/ownadx.json new file mode 100644 index 00000000000..f529e74cb01 --- /dev/null +++ b/static/bidder-params/ownadx.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "OwnAdx Adapter Params", + "description": "A schema which validates params accepted by the OwnAdx adapter", + "type": "object", + + "properties": { + "sspId": { + "type": "string", + "description": "Ssp ID" + }, + "seatId": { + "type": "string", + "description": "Seat ID" + }, + "tokenId": { + "type": "string", + "description": "Token ID" + } + }, + + "oneOf": [ + { "required": ["sspId"] }, + { "required": ["feedId"] }, + { "required": ["token"] } + ] +} diff --git a/static/bidder-params/pgam.json b/static/bidder-params/pgam.json deleted file mode 100644 index c0a016a7d24..00000000000 --- a/static/bidder-params/pgam.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "PGAM Adapter Params", - "description": "A schema which validates params accepted by the PGAM ssp adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} diff --git a/static/bidder-params/pgamssp.json b/static/bidder-params/pgamssp.json new file mode 100644 index 00000000000..9d060241908 --- /dev/null +++ b/static/bidder-params/pgamssp.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "PgamSSP Adapter Params", + "description": "A schema which validates params accepted by the PgamSSP adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file diff --git a/static/bidder-params/quantumdex.json b/static/bidder-params/quantumdex.json deleted file mode 100644 index 3ef9abe34c6..00000000000 --- a/static/bidder-params/quantumdex.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Quantumdex Adapter Params", - "description": "A schema which validates params accepted by the Quantumdex adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "Placement ID provided by Quantumdex" - }, - "siteId": { - "type": "string", - "description": "Publisher site ID from Quantumdex" - }, - "floorPrice": { - "type": "number", - "description": "CPM bidfloor in USD" - } - }, - "oneOf": [ - { - "required": [ - "placementId" - ] - }, - { - "required": [ - "siteId" - ] - } - ] -} \ No newline at end of file diff --git a/static/bidder-params/relevantdigital.json b/static/bidder-params/relevantdigital.json new file mode 100644 index 00000000000..5f88d85fe58 --- /dev/null +++ b/static/bidder-params/relevantdigital.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Relevant Digital Adapter Params", + "description": "A schema which validates params accepted by the Relevant Digital adapter", + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "An ID which identifies the Relevant Digital account ID" + }, + "placementId": { + "type": "string", + "description": "An ID which identifies the Relevant Digital placement ID" + }, + "pbsHost": { + "type": "string", + "description": "Prebid Server Host supplied by Relevant Digital" + }, + "pbsBufferMs": { + "type": "number", + "description": "TMax buffer, default is 250" + } + }, + "required": [ + "accountId", + "placementId", + "pbsHost" + ] +} diff --git a/static/bidder-params/rhythmone.json b/static/bidder-params/rhythmone.json deleted file mode 100644 index 01366b45607..00000000000 --- a/static/bidder-params/rhythmone.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Rhythmone Adapter Params", - "description": "A schema which validates params accepted by the Rhythmone adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - }, - "path": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - }, - "zone": { - "type": "string", - "description": "An ID which is used to frame Rhythmone ad tag", - "minLength": 1 - } - }, - "required": ["placementId", "path", "zone"] -} diff --git a/static/bidder-params/rise.json b/static/bidder-params/rise.json index 8e745d89fd0..c5344b7ab0f 100644 --- a/static/bidder-params/rise.json +++ b/static/bidder-params/rise.json @@ -4,10 +4,17 @@ "description": "A schema which validates params accepted by the Rise adapter", "type": "object", "properties": { + "org": { + "type": "string", + "description": "The organization ID." + }, "publisher_id": { "type": "string", - "description": "The publisher ID" + "description": "Deprecated, use org instead." } }, - "required": ["publisher_id"] + "oneOf": [ + { "required": ["org"] }, + { "required": ["publisher_id"] } + ] } diff --git a/static/bidder-params/rtbhouse.json b/static/bidder-params/rtbhouse.json index 00732bedd2f..727f51bcc30 100644 --- a/static/bidder-params/rtbhouse.json +++ b/static/bidder-params/rtbhouse.json @@ -7,6 +7,18 @@ "publisherId": { "type": "string", "description": "The publisher’s ID provided by RTB House" + }, + "region": { + "type": "string", + "description": "The publisher’s assigned datacenter region" + }, + "bidfloor": { + "type": "number", + "description": "(optional) Minimum bid price in CPM that will be considered" + }, + "channel": { + "type": "string", + "description": "Inventory channel identifier, limited to 50 characters" } }, "required": ["publisherId"] diff --git a/static/bidder-params/screencore.json b/static/bidder-params/screencore.json new file mode 100644 index 00000000000..0a181b20f1c --- /dev/null +++ b/static/bidder-params/screencore.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Screencore Adapter Params", + "description": "A schema which validates params accepted by the Screencore adapter", + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "Account id", + "minLength": 1 + }, + "placementId": { + "type": "string", + "description": "Placement id", + "minLength": 1 + } + }, + "required": [ + "accountId", + "placementId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/seedingAlliance.json b/static/bidder-params/seedingAlliance.json index cf9aa375eb4..5fe463b6803 100644 --- a/static/bidder-params/seedingAlliance.json +++ b/static/bidder-params/seedingAlliance.json @@ -8,6 +8,10 @@ "type": "string", "description": "Ad Unit ID", "minLength": 1 + }, + "seatId": { + "type": "string", + "description": "Seat ID" } }, "required": [ diff --git a/static/bidder-params/silverpush.json b/static/bidder-params/silverpush.json new file mode 100644 index 00000000000..36dec33bb92 --- /dev/null +++ b/static/bidder-params/silverpush.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Silverpush OpenRTB Bidder Adapter", + "description": "A schema which validates params accepted by the SilverPush adapter", + "type": "object", + + "properties": { + "publisherId": { + "type":"string", + "description": "Publisher id provided by silverpush" + }, + "bidfloor": { + "type":"number", + "description": "Minimum price in USD. bidFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50." + } + }, + + "required": ["publisherId"] + } \ No newline at end of file diff --git a/static/bidder-params/smartx.json b/static/bidder-params/smartx.json new file mode 100644 index 00000000000..3bd97456770 --- /dev/null +++ b/static/bidder-params/smartx.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "smartclip.tv Adapter Params", + "description": "A schema which validates params accepted by the smartclip.tv adapter", + "type": "object", + "properties": { + "tagId": { + "type": "string", + "description": "Ad tag ID" + }, + "publisherId": { + "type": "string", + "description": "Publisher ID" + }, + "siteId": { + "type": "string", + "description": "Site ID" + }, + "appId": { + "type": "string", + "description": "App ID" + }, + "bundleId": { + "type": "string", + "description": "Bundle ID" + }, + "storeUrl": { + "type": "string", + "description": "AppStore URL" + } + }, + "oneOf": [ + { "required": ["siteId"] }, + { "required": ["appId"] } + ], + "required": ["tagId", "publisherId"] +} \ No newline at end of file diff --git a/static/bidder-params/sovrn.json b/static/bidder-params/sovrn.json index 6b345b5562b..d821a808f97 100644 --- a/static/bidder-params/sovrn.json +++ b/static/bidder-params/sovrn.json @@ -16,6 +16,10 @@ "bidfloor": { "type": "number", "description": "The minimium acceptable bid, in CPM, using US Dollars" + }, + "adunitcode": { + "type": "string", + "description": "The string which identifies Ad Unit" } }, "oneOf": [ diff --git a/static/bidder-params/sovrnXsp.json b/static/bidder-params/sovrnXsp.json new file mode 100644 index 00000000000..beea3f588df --- /dev/null +++ b/static/bidder-params/sovrnXsp.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sovrn XSP Adapter Params", + "description": "Schema validating params accepted by the Sovrn XSP adapter", + "type": "object", + "properties": { + "pub_id": { + "type": "string", + "description": "Assigned publisher ID", + "minLength": 4 + }, + "med_id": { + "type": "string", + "description": "Property ID not zone ID not provided" + }, + "zone_id": { + "type": "string", + "description": "Specific zone ID for this placement, belonging to app/site", + "minLength": 20 + }, + "force_bid": { + "type": "boolean", + "description": "Force bids with a test creative" + } + }, + "required": [ "pub_id" ] + } diff --git a/static/bidder-params/streamkey.json b/static/bidder-params/streamkey.json deleted file mode 100644 index ec8e5b1b643..00000000000 --- a/static/bidder-params/streamkey.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Streamkey Adapter Params", - "description": "A schema which validates params accepted by the Streamkey adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} diff --git a/static/bidder-params/synacormedia.json b/static/bidder-params/synacormedia.json deleted file mode 100644 index 21eb06cc7ce..00000000000 --- a/static/bidder-params/synacormedia.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Synacormedia Adapter Params", - "description": "DEPRECATED: Use imds bidder instead. A schema which validates params accepted by the Synacormedia adapter", - - "type": "object", - "properties": { - "seatId": { - "type": "string", - "description": "The seat id." - }, - "tagId": { - "type": "string", - "description": "The tag id." - } - }, - - "required": ["seatId"] -} diff --git a/static/bidder-params/teads.json b/static/bidder-params/teads.json new file mode 100644 index 00000000000..2b3f47d8bc2 --- /dev/null +++ b/static/bidder-params/teads.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Teads Adapter Params", + "description": "A schema which validates params accepted by the Teads adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "The placement id.", + "minimum": 1 + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/static/bidder-params/freewheel-ssp.json b/static/bidder-params/tpmn.json similarity index 53% rename from static/bidder-params/freewheel-ssp.json rename to static/bidder-params/tpmn.json index 103aed03198..68b54373179 100644 --- a/static/bidder-params/freewheel-ssp.json +++ b/static/bidder-params/tpmn.json @@ -1,15 +1,16 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "FreewheelSSP old Adapter Params", - "description": "A schema which validates params accepted by the FreewheelSSP adapter", + "title": "TPMN Adapter Params", + "description": "A schema which validates params accepted by the TPMN adapter", "type": "object", - "properties": { - "zoneId": { + "inventoryId": { + "description": "Inventory ID", "type": "integer", - "description": "Zone ID" + "minLength": 1 } }, - - "required": ["zoneId"] -} + "required": [ + "inventoryId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/trustx.json b/static/bidder-params/trustx.json deleted file mode 100644 index efedf9de537..00000000000 --- a/static/bidder-params/trustx.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TrustX Adapter Params", - "description": "A schema which validates params accepted by TrustX adapter", - "type": "object", - "properties": { - "uid": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - } - }, - "required": [] -} diff --git a/static/bidder-params/valueimpression.json b/static/bidder-params/valueimpression.json deleted file mode 100644 index 793e940eb11..00000000000 --- a/static/bidder-params/valueimpression.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Valueimpression Adapter Params", - "description": "A schema which validates params accepted by the Valueimpression adapter", - "type": "object", - "properties": { - "placementId": { - "type": "string", - "description": "Placement ID provided by Valueimpression" - }, - "siteId": { - "type": "string", - "description": "Publisher site ID from Valueimpression" - }, - "floorPrice": { - "type": "number", - "description": "CPM bidfloor in USD" - } - }, - "oneOf": [ - { - "required": [ - "placementId" - ] - }, - { - "required": [ - "siteId" - ] - } - ] -} \ No newline at end of file diff --git a/static/bidder-params/viewdeos.json b/static/bidder-params/viewdeos.json deleted file mode 100644 index 3e309e4b77a..00000000000 --- a/static/bidder-params/viewdeos.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Viewdeos Adapter Params", - "description": "A schema which validates params accepted by the Viewdeos adapter", - - "type": "object", - "properties": { - "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" - }, - "aid": { - "type": "integer", - "description": "An ID which identifies the channel" - }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" - } - }, - "required": ["aid"] -} diff --git a/static/bidder-params/vox.json b/static/bidder-params/vox.json new file mode 100644 index 00000000000..2ae65e99fc7 --- /dev/null +++ b/static/bidder-params/vox.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Vox Adapter Params", + "description": "A schema which validates params accepted by the Vox adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "description": "Placement ID" + }, + "imageUrl": { + "type": "string", + "description": "An URL of image on which banner should be put(InImage Ad format)." + }, + "displaySizes": { + "type": "array", + "items": { + "type": "string", + "description": "x(Example: 123x90, 720x100)" + }, + "description": "Display banner sizes which could be shown on top of image." + } + }, + + "required": ["placementId"] +} \ No newline at end of file diff --git a/static/bidder-params/yahoossp.json b/static/bidder-params/yahooAds.json similarity index 87% rename from static/bidder-params/yahoossp.json rename to static/bidder-params/yahooAds.json index 9d971149339..77e7107350c 100644 --- a/static/bidder-params/yahoossp.json +++ b/static/bidder-params/yahooAds.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "YahooSSP Adapter Params", - "description": "A schema which validates params accepted by the YahooSSP adapter", + "title": "YahooAds Adapter Params", + "description": "A schema which validates params accepted by the YahooAds adapter", "type": "object", "properties": { "dcn": { diff --git a/static/bidder-params/yahooAdvertising.json b/static/bidder-params/yahooAdvertising.json deleted file mode 100644 index 4778f0778c7..00000000000 --- a/static/bidder-params/yahooAdvertising.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "YahooAdvertising Adapter Params", - "description": "A schema which validates params accepted by the YahooAdvertising adapter", - "type": "object", - "properties": { - "dcn": { - "type": "string", - "minLength": 1, - "description": "Site ID provided by One Mobile" - }, - "pos": { - "type": "string", - "minLength": 1, - "description": "Placement ID" - } - }, - "required": ["dcn", "pos"] -} diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index 3ea36bc0fc7..3a9b83f7779 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -7,8 +7,8 @@ import ( "github.com/lib/pq" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" ) func NewFetcher( diff --git a/stored_requests/backends/db_fetcher/fetcher_test.go b/stored_requests/backends/db_fetcher/fetcher_test.go index 04753fb8af5..f736e1bea15 100644 --- a/stored_requests/backends/db_fetcher/fetcher_test.go +++ b/stored_requests/backends/db_fetcher/fetcher_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/db_provider.go b/stored_requests/backends/db_provider/db_provider.go index 0f79a7737e0..6e623356ed8 100644 --- a/stored_requests/backends/db_provider/db_provider.go +++ b/stored_requests/backends/db_provider/db_provider.go @@ -5,7 +5,7 @@ import ( "database/sql" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type DbProvider interface { diff --git a/stored_requests/backends/db_provider/db_provider_mock.go b/stored_requests/backends/db_provider/db_provider_mock.go index 3d4cfda76c3..c0ec3458eee 100644 --- a/stored_requests/backends/db_provider/db_provider_mock.go +++ b/stored_requests/backends/db_provider/db_provider_mock.go @@ -6,7 +6,7 @@ import ( "reflect" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) func NewDbProviderMock() (*DbProviderMock, sqlmock.Sqlmock, error) { diff --git a/stored_requests/backends/db_provider/mysql_dbprovider.go b/stored_requests/backends/db_provider/mysql_dbprovider.go index 9e1b2e3ea3b..38ad0836024 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider.go @@ -8,14 +8,14 @@ import ( "database/sql" "errors" "fmt" - "io/ioutil" + "os" "regexp" "sort" "strconv" "strings" "github.com/go-sql-driver/mysql" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) const customTLSKey = "prebid-tls" @@ -106,7 +106,7 @@ func (provider *MySqlDbProvider) ConnString() (string, error) { func setupTLSConfig(provider *MySqlDbProvider) error { rootCertPool := x509.NewCertPool() - pem, err := ioutil.ReadFile(provider.cfg.TLS.RootCert) + pem, err := os.ReadFile(provider.cfg.TLS.RootCert) if err != nil { return err } diff --git a/stored_requests/backends/db_provider/mysql_dbprovider_test.go b/stored_requests/backends/db_provider/mysql_dbprovider_test.go index e47280ef26b..b91352d08d6 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider_test.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider_test.go @@ -6,7 +6,7 @@ import ( "runtime" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/postgres_dbprovider.go b/stored_requests/backends/db_provider/postgres_dbprovider.go index ef945faebc9..e2081f27df4 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type PostgresDbProvider struct { diff --git a/stored_requests/backends/db_provider/postgres_dbprovider_test.go b/stored_requests/backends/db_provider/postgres_dbprovider_test.go index 9e98c0b5763..4b31e6f8ec3 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider_test.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go index 0246990c02e..c851a997df9 100644 --- a/stored_requests/backends/empty_fetcher/fetcher.go +++ b/stored_requests/backends/empty_fetcher/fetcher.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // EmptyFetcher is a nil-object which has no Stored Requests. diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index 56f5bdf853c..21a54039cda 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -7,7 +7,8 @@ import ( "os" "strings" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -78,7 +79,7 @@ func (fetcher *eagerFetcher) FetchCategories(ctx context.Context, primaryAdServe tmp := make(map[string]stored_requests.Category) - if err := json.Unmarshal(file, &tmp); err != nil { + if err := jsonutil.UnmarshalValid(file, &tmp); err != nil { return "", fmt.Errorf("Unable to unmarshal categories for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) } fetcher.Categories[fileName] = tmp diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index 3c585f9f456..0155c1aa82c 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -6,7 +6,8 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -58,7 +59,7 @@ func validateStoredReqOne(t *testing.T, storedRequests map[string]json.RawMessag } var req1Val map[string]string - if err := json.Unmarshal(value, &req1Val); err != nil { + if err := jsonutil.UnmarshalValid(value, &req1Val); err != nil { t.Errorf("Failed to unmarshal 1: %v", err) } if len(req1Val) != 1 { @@ -80,7 +81,7 @@ func validateStoredReqTwo(t *testing.T, storedRequests map[string]json.RawMessag } var req2Val string - if err := json.Unmarshal(value, &req2Val); err != nil { + if err := jsonutil.UnmarshalValid(value, &req2Val); err != nil { t.Errorf("Failed to unmarshal %d: %v", 2, err) } if req2Val != `esca"ped` { @@ -95,7 +96,7 @@ func validateImp(t *testing.T, storedImps map[string]json.RawMessage) { } var impVal map[string]bool - if err := json.Unmarshal(value, &impVal); err != nil { + if err := jsonutil.UnmarshalValid(value, &impVal); err != nil { t.Errorf("Failed to unmarshal some-imp: %v", err) } if len(impVal) != 1 { diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 326b63fce71..75aa1090ce4 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -1,7 +1,6 @@ package http_fetcher import ( - "bytes" "context" "encoding/json" "fmt" @@ -10,7 +9,8 @@ import ( "net/url" "strings" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" "github.com/golang/glog" @@ -142,7 +142,7 @@ func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []stri } } var responseData accountsResponseContract - if err = json.Unmarshal(respBytes, &responseData); err != nil { + if err = jsonutil.UnmarshalValid(respBytes, &responseData); err != nil { return nil, []error{ fmt.Errorf(`Error fetching accounts %v via http: failed to parse response: %v`, accountIDs, err), } @@ -209,7 +209,7 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer respBytes, err := io.ReadAll(httpResp.Body) tmp := make(map[string]stored_requests.Category) - if err := json.Unmarshal(respBytes, &tmp); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &tmp); err != nil { return "", fmt.Errorf("Unable to unmarshal categories for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) } fetcher.Categories[dataName] = tmp @@ -240,7 +240,7 @@ func unpackResponse(resp *http.Response) (requestData map[string]json.RawMessage if resp.StatusCode == http.StatusOK { var responseObj responseContract - if err := json.Unmarshal(respBytes, &responseObj); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &responseObj); err != nil { errs = append(errs, err) return } @@ -260,7 +260,7 @@ func unpackResponse(resp *http.Response) (requestData map[string]json.RawMessage func convertNullsToErrs(m map[string]json.RawMessage, dataType string, errs []error) []error { for id, val := range m { - if bytes.Equal(val, []byte("null")) { + if val == nil { delete(m, id) errs = append(errs, stored_requests.NotFoundError{ ID: id, diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 3c8ca7cb070..1358ce413c1 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -233,7 +234,7 @@ func newHandler(t *testing.T, expectReqIDs []string, expectImpIDs []string, json Imps: impIDResponse, } - if respBytes, err := json.Marshal(respObj); err != nil { + if respBytes, err := jsonutil.Marshal(respObj); err != nil { t.Errorf("failed to marshal responseContract in test: %v", err) w.WriteHeader(http.StatusInternalServerError) } else { @@ -267,7 +268,7 @@ func newAccountHandler(t *testing.T, expectAccIDs []string) func(w http.Response Accounts: accIDResponse, } - if respBytes, err := json.Marshal(respObj); err != nil { + if respBytes, err := jsonutil.Marshal(respObj); err != nil { t.Errorf("failed to marshal responseContract in test: %v", err) w.WriteHeader(http.StatusInternalServerError) } else { @@ -327,7 +328,7 @@ func richSplit(queryVal string) []string { } func jsonifyID(id string) json.RawMessage { - if b, err := json.Marshal(id); err != nil { + if b, err := jsonutil.Marshal(id); err != nil { return json.RawMessage([]byte("\"error encoding ID=" + id + "\"")) } else { return json.RawMessage(b) diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go index 7fbaf7238af..517668318c7 100644 --- a/stored_requests/caches/cachestest/reliable.go +++ b/stored_requests/caches/cachestest/reliable.go @@ -5,7 +5,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) const ( diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go index 5939c26ddec..eb6317a3487 100644 --- a/stored_requests/caches/memory/cache.go +++ b/stored_requests/caches/memory/cache.go @@ -7,7 +7,7 @@ import ( "github.com/coocood/freecache" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // NewCache returns an in-memory Cache which evicts items if: diff --git a/stored_requests/caches/memory/cache_test.go b/stored_requests/caches/memory/cache_test.go index b89bd5af26f..67ff661c7c5 100644 --- a/stored_requests/caches/memory/cache_test.go +++ b/stored_requests/caches/memory/cache_test.go @@ -7,8 +7,8 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/cachestest" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/cachestest" ) func TestLRURobustness(t *testing.T) { diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 9cb349d1f72..112b11122f9 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -5,24 +5,24 @@ import ( "net/http" "time" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" - "github.com/prebid/prebid-server/stored_requests/events" - apiEvents "github.com/prebid/prebid-server/stored_requests/events/api" - databaseEvents "github.com/prebid/prebid-server/stored_requests/events/database" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v2/stored_requests/events" + apiEvents "github.com/prebid/prebid-server/v2/stored_requests/events/api" + databaseEvents "github.com/prebid/prebid-server/v2/stored_requests/events/database" + httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" + "github.com/prebid/prebid-server/v2/util/task" ) // CreateStoredRequests returns three things: diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index b06feea7d31..2e5a8c2a079 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -13,14 +13,14 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/events" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v2/stored_requests/events" + httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" "github.com/stretchr/testify/mock" ) diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index bf8edd2d849..2d489bded38 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -1,12 +1,12 @@ package api import ( - "encoding/json" "io" "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) type eventsAPI struct { @@ -43,7 +43,7 @@ func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ http } var save events.Save - if err := json.Unmarshal(body, &save); err != nil { + if err := jsonutil.UnmarshalValid(body, &save); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Invalid update.\n")) return @@ -59,7 +59,7 @@ func (api *eventsAPI) HandleEvent(w http.ResponseWriter, r *http.Request, _ http } var invalidation events.Invalidation - if err := json.Unmarshal(body, &invalidation); err != nil { + if err := jsonutil.UnmarshalValid(body, &invalidation); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Invalid invalidation.\n")) return diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go index 67ba836cb00..536afe820af 100644 --- a/stored_requests/events/api/api_test.go +++ b/stored_requests/events/api/api_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests/events" ) func TestGoodRequests(t *testing.T) { diff --git a/stored_requests/events/database/database.go b/stored_requests/events/database/database.go index 24eddf214eb..260a58029cb 100644 --- a/stored_requests/events/database/database.go +++ b/stored_requests/events/database/database.go @@ -9,11 +9,11 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/timeutil" ) func bytesNull() []byte { diff --git a/stored_requests/events/database/database_test.go b/stored_requests/events/database/database_test.go index 8ce21bfde95..27a56b67fe3 100644 --- a/stored_requests/events/database/database_test.go +++ b/stored_requests/events/database/database_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v2/stored_requests/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 725df4279d8..e042b7c3513 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests" ) // Save represents a bulk save diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index 580f1eddf37..165e6a6beb0 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -7,8 +7,8 @@ import ( "reflect" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v2/stored_requests" + "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" ) func TestListen(t *testing.T) { diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index 1c4f8fdff73..6c2145da2f3 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -12,7 +12,8 @@ import ( "golang.org/x/net/context/ctxhttp" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v2/stored_requests/events" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/golang/glog" ) @@ -184,7 +185,7 @@ func (e *HTTPEvents) parse(endpoint string, resp *httpCore.Response, err error) } var respObj responseContract - if err := json.Unmarshal(respBytes, &respObj); err != nil { + if err := jsonutil.UnmarshalValid(respBytes, &respObj); err != nil { glog.Errorf("Failed to unmarshal body of GET %s for Stored Requests: %v", endpoint, err) return nil, false } diff --git a/stored_requests/events/http/http_test.go b/stored_requests/events/http/http_test.go index a185a3c2360..fa7f64f227f 100644 --- a/stored_requests/events/http/http_test.go +++ b/stored_requests/events/http/http_test.go @@ -2,13 +2,13 @@ package http import ( "context" - "encoding/json" "fmt" httpCore "net/http" "net/http/httptest" "testing" "time" + "github.com/prebid/prebid-server/v2/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -151,14 +151,14 @@ func TestStartup(t *testing.T) { t.Run(fmt.Sprintf("Step %d", i+1), func(t *testing.T) { // Check expected Saves if len(test.saves) > 0 { - saves, err := json.Marshal(<-ev.Saves()) + saves, err := jsonutil.Marshal(<-ev.Saves()) assert.NoError(t, err, `Failed to marshal event.Save object: %v`, err) assert.JSONEq(t, test.saves, string(saves)) } assert.Empty(t, ev.Saves(), "Unexpected additional messages in save channel") // Check expected Invalidations if len(test.invalidations) > 0 { - invalidations, err := json.Marshal(<-ev.Invalidations()) + invalidations, err := jsonutil.Marshal(<-ev.Invalidations()) assert.NoError(t, err, `Failed to marshal event.Invalidation object: %v`, err) assert.JSONEq(t, test.invalidations, string(invalidations)) } diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 433b33427b8..cd3855c9eb8 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v2/metrics" ) // Fetcher knows how to fetch Stored Request data by id. diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go index 684b867165c..34c15f61f96 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -6,8 +6,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v2/metrics" + "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index 19b010fb12d..802602904bc 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "fmt" + "strings" - "github.com/buger/jsonparser" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/stored_requests" ) type ImpsWithAuctionResponseIDs map[string]string @@ -56,8 +56,7 @@ func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidRe return bidderToImpToResponses } -func extractStoredResponsesIds(impInfo []ImpExtPrebidData, - bidderMap map[string]openrtb_ext.BidderName) ( +func extractStoredResponsesIds(impInfo []*openrtb_ext.ImpWrapper) ( StoredResponseIDs, ImpBiddersWithBidResponseIDs, ImpsWithAuctionResponseIDs, @@ -75,48 +74,62 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, impBidderReplaceImp := ImpBidderReplaceImpID{} for index, impData := range impInfo { - impId, err := jsonparser.GetString(impData.Imp, "id") + impId := impData.ID + impExt, err := impData.GetImpExt() if err != nil { - return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index) + return nil, nil, nil, nil, err + } + impExtPrebid := impExt.GetPrebid() + if impExtPrebid == nil { + continue } - if impData.ImpExtPrebid.StoredAuctionResponse != nil { - if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 { + if impExtPrebid.StoredAuctionResponse != nil { + if len(impExtPrebid.StoredAuctionResponse.ID) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index) } - allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID) + allStoredResponseIDs = append(allStoredResponseIDs, impExtPrebid.StoredAuctionResponse.ID) - impAuctionResponseIDs[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID + impAuctionResponseIDs[impId] = impExtPrebid.StoredAuctionResponse.ID } - if len(impData.ImpExtPrebid.StoredBidResponse) > 0 { + if len(impExtPrebid.StoredBidResponse) > 0 { + + // bidders can be specified in imp.ext and in imp.ext.prebid.bidders + allBidderNames := make([]string, 0) + for bidderName := range impExtPrebid.Bidder { + allBidderNames = append(allBidderNames, bidderName) + } + for extData := range impExt.GetExt() { + // no bidders will not be processed + allBidderNames = append(allBidderNames, extData) + } bidderStoredRespId := make(map[string]string) bidderReplaceImpId := make(map[string]bool) - for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse { + for _, bidderResp := range impExtPrebid.StoredBidResponse { if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) } - //check if bidder is valid/exists - if _, isValid := bidderMap[bidderResp.Bidder]; !isValid { - return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) - } - // bidder is unique per one bid stored response - // if more than one bidder specified the last defined bidder id will take precedence - bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID - impBiddersWithBidResponseIDs[impId] = bidderStoredRespId - - // stored response config can specify if imp id should be replaced with imp id from request - replaceImpId := true - if bidderResp.ReplaceImpId != nil { - // replaceimpid is true if not specified - replaceImpId = *bidderResp.ReplaceImpId - } - bidderReplaceImpId[bidderResp.Bidder] = replaceImpId - impBidderReplaceImp[impId] = bidderReplaceImpId - //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids - allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + for _, bidderName := range allBidderNames { + if _, found := bidderStoredRespId[bidderName]; !found && strings.EqualFold(bidderName, bidderResp.Bidder) { + bidderStoredRespId[bidderName] = bidderResp.ID + impBiddersWithBidResponseIDs[impId] = bidderStoredRespId + + // stored response config can specify if imp id should be replaced with imp id from request + replaceImpId := true + if bidderResp.ReplaceImpId != nil { + // replaceimpid is true if not specified + replaceImpId = *bidderResp.ReplaceImpId + } + bidderReplaceImpId[bidderName] = replaceImpId + impBidderReplaceImp[impId] = bidderReplaceImpId + + //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids + allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + } + } } } } @@ -129,14 +142,11 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, // Note that processStoredResponses must be called after processStoredRequests // because stored imps and stored requests can contain stored auction responses and stored bid responses // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses. -func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { - impInfo, errs := parseImpInfo(requestJson) - if len(errs) > 0 { - return nil, nil, nil, errs - } - storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(impInfo, bidderMap) +func ProcessStoredResponses(ctx context.Context, requestWrapper *openrtb_ext.RequestWrapper, storedRespFetcher stored_requests.Fetcher) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { + + storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(requestWrapper.GetImp()) if err != nil { - return nil, nil, nil, append(errs, err) + return nil, nil, nil, []error{err} } if len(storedResponsesIds) > 0 { @@ -195,28 +205,3 @@ func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, } return impIdToStoredResp, impBidderToStoredBidResponse, errs } - -// parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid -// copied from exchange to isolate stored responses code from auction dependencies -func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { - - if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { - _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { - impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") - var impExtPrebid openrtb_ext.ExtImpPrebid - if impExtData != nil { - if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { - errs = append(errs, err) - } - } - newImpData := ImpExtPrebidData{imp, impExtPrebid} - impData = append(impData, newImpData) - }) - } - return -} - -type ImpExtPrebidData struct { - Imp json.RawMessage - ImpExtPrebid openrtb_ext.ExtImpPrebid -} diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index c4ddea278a7..196466b7323 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -163,136 +163,109 @@ func TestBuildStoredBidResponses(t *testing.T) { } func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"testBidder": "testBidder"} - testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrorList []error }{ { description: "Invalid stored auction response format: empty stored Auction Response Id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { - } - } - } - } - ]}`), + "storedauctionresponse": {} + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.bidder", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "id": "123abc"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "bidder": "testbidder"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, - { - description: "Invalid stored bid response: storedbidresponse.bidder not found", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - "storedbidresponse": [ - { "bidder": "testBidder123", "id": "123abc"}] - } - } - } - ]}`), - expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext.prebid.bidder contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")}, - }, { description: "Invalid stored auction response format: empty stored Auction Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedauctionresponse": { "id":"123" } - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { + "storedauctionresponse": { "id":"" } - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty stored bid Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": "123abc"} ] - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": ""} ] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + rw := &openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(nil, rw, nil) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } func TestProcessStoredAuctionAndBidResponses(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) bidStoredResp3 := json.RawMessage(`[{"bid": [{"id": "bid_id3"],"seat": "bidderA"}]`) @@ -305,33 +278,31 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedStoredAuctionResponses ImpsWithBidResponses expectedStoredBidResponses ImpBidderStoredResp expectedBidderImpReplaceImpID BidderImpReplaceImpID }{ { description: "No stored responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - - } - } - } - ]}`), + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "prebid": {} + }`)}, + }, + }, expectedStoredAuctionResponses: nil, expectedStoredBidResponses: nil, expectedBidderImpReplaceImpID: nil, }, { description: "Stored auction response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -340,9 +311,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -351,11 +322,11 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { "placementId": 123 }, "prebid": { @@ -363,9 +334,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderA", "id": "1"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1}, @@ -376,11 +347,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -389,9 +363,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -401,14 +375,120 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "bidderB": map[string]bool{"imp-id1": false}, }, }, + { + description: "Stored bid responses two same mixed case bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp, duplicated stored response", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderA", "id": "2", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, { //This is not a valid scenario for real auction request, added for testing purposes description: "Stored auction and bid responses one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -420,9 +500,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -436,10 +516,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -448,11 +528,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -461,11 +539,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -474,9 +551,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -487,10 +565,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps duplicated stored auction response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -499,11 +577,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -512,11 +588,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -525,9 +600,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -538,11 +614,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders two imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -551,12 +630,13 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -565,9 +645,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -581,17 +661,19 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { } for _, test := range testCases { - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses, "storedAuctionResponses doesn't match: %s\n", test.description) - assert.Equalf(t, test.expectedStoredBidResponses, storedBidResponses, "storedBidResponses doesn't match: %s\n", test.description) - assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId, "bidderImpReplaceImpId doesn't match: %s\n", test.description) - assert.Nil(t, errorList, "Error should be nil") + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, &rw, fetcher) + assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses) + assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses) + assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId) + assert.Nil(t, errorList, "Error should be nil") + }) } } func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) mockStoredResponses := map[string]json.RawMessage{ @@ -604,16 +686,16 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrors []error }{ { description: "Stored bid response with nil data, one bidder one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -621,20 +703,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, one with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -642,12 +724,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "1"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -655,20 +735,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, both with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -676,12 +756,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "4"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -689,9 +767,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 4"), errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), @@ -699,10 +777,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data and one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -711,19 +789,19 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), }, }, { description: "Stored auction response with nil data, and two imps with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -732,11 +810,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -745,9 +821,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), @@ -755,10 +831,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data, two imps, one with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -767,11 +843,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "2" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -780,9 +854,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), }, @@ -790,10 +864,13 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - for _, err := range test.expectedErrors { - assert.Contains(t, errorList, err, "incorrect errors returned: %s", test.description) - } + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(nil, &rw, fetcher) + for _, err := range test.expectedErrors { + assert.Contains(t, errorList, err) + } + }) } } @@ -847,8 +924,10 @@ func TestFlipMap(t *testing.T) { } for _, test := range testCases { - actualResult := flipMap(test.inImpBidderReplaceImpID) - assert.Equal(t, test.outBidderImpReplaceImpID, actualResult, "Incorrect flipped map for test case %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + actualResult := flipMap(test.inImpBidderReplaceImpID) + assert.Equal(t, test.outBidderImpReplaceImpID, actualResult) + }) } } diff --git a/usersync/bidderfilter.go b/usersync/bidderfilter.go index 2d7d16ffe2b..2e4df476f26 100644 --- a/usersync/bidderfilter.go +++ b/usersync/bidderfilter.go @@ -1,5 +1,9 @@ package usersync +import ( + "strings" +) + // BidderFilter determines if a bidder has permission to perform a user sync activity. type BidderFilter interface { // Allowed returns true if the filter determines the bidder has permission and false if either @@ -40,7 +44,7 @@ func (f SpecificBidderFilter) Allowed(bidder string) bool { func NewSpecificBidderFilter(bidders []string, mode BidderFilterMode) BidderFilter { biddersLookup := make(map[string]struct{}, len(bidders)) for _, bidder := range bidders { - biddersLookup[bidder] = struct{}{} + biddersLookup[strings.ToLower(bidder)] = struct{}{} } return SpecificBidderFilter{biddersLookup: biddersLookup, mode: mode} diff --git a/usersync/bidderfilter_test.go b/usersync/bidderfilter_test.go index ddc757339a2..009c051bacc 100644 --- a/usersync/bidderfilter_test.go +++ b/usersync/bidderfilter_test.go @@ -1,6 +1,7 @@ package usersync import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -69,6 +70,12 @@ func TestSpecificBidderFilter(t *testing.T) { mode: BidderFilterMode(-1), expected: false, }, + { + description: "Case Insensitive Include - One", + bidders: []string{strings.ToUpper(bidder)}, + mode: BidderFilterModeInclude, + expected: true, + }, } for _, test := range testCases { diff --git a/usersync/chooser.go b/usersync/chooser.go index 97fa1471b7e..d8bf731f693 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -1,5 +1,12 @@ package usersync +import ( + "strings" + + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" +) + // Chooser determines which syncers are eligible for a given request. type Chooser interface { // Choose considers bidders to sync, filters the bidders, and returns the result of the @@ -8,16 +15,20 @@ type Chooser interface { } // NewChooser returns a new instance of the standard chooser implementation. -func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { +func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}, bidderInfo map[string]config.BidderInfo) Chooser { bidders := make([]string, 0, len(bidderSyncerLookup)) + for k := range bidderSyncerLookup { bidders = append(bidders, k) } return standardChooser{ - bidderSyncerLookup: bidderSyncerLookup, - biddersAvailable: bidders, - bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: bidders, + bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, + normalizeValidBidderName: openrtb_ext.NormalizeBidderName, + biddersKnown: biddersKnown, + bidderInfo: bidderInfo, } } @@ -28,6 +39,8 @@ type Request struct { Limit int Privacy Privacy SyncTypeFilter SyncTypeFilter + GPPSID string + Debug bool } // Cooperative specifies the settings for cooperative syncing for a given request, where bidders @@ -67,13 +80,6 @@ const ( // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. StatusBlockedByUserOptOut - // StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies - // or specific bidder syncing. - StatusBlockedByGDPR - - // StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. - StatusBlockedByCCPA - // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. StatusAlreadySynced @@ -85,20 +91,37 @@ const ( // StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice. StatusDuplicate + + // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities + StatusBlockedByPrivacy + + // StatusBlockedByRegulationScope specifies the bidder chose to not sync given GDPR being in scope or because of a GPPSID + StatusBlockedByRegulationScope + + // StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server + StatusUnconfiguredBidder + + // StatusBlockedByDisabledUsersync refers to a bidder who won't be synced because it's been disabled in its config by the host + StatusBlockedByDisabledUsersync ) // Privacy determines which privacy policies will be enforced for a user sync request. type Privacy interface { GDPRAllowsHostCookie() bool + GDPRInScope() bool GDPRAllowsBidderSync(bidder string) bool CCPAAllowsBidderSync(bidder string) bool + ActivityAllowsUserSync(bidder string) bool } // standardChooser implements the user syncer algorithm per official Prebid specification. type standardChooser struct { - bidderSyncerLookup map[string]Syncer - biddersAvailable []string - bidderChooser bidderChooser + bidderSyncerLookup map[string]Syncer + biddersAvailable []string + bidderChooser bidderChooser + normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) + biddersKnown map[string]struct{} + bidderInfo map[string]config.BidderInfo } // Choose randomly selects user syncers which are permitted by the user's privacy settings and @@ -109,10 +132,11 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { } if !request.Privacy.GDPRAllowsHostCookie() { - return Result{Status: StatusBlockedByGDPR} + return Result{Status: StatusBlockedByPrivacy} } syncersSeen := make(map[string]struct{}) + biddersSeen := make(map[string]struct{}) limitDisabled := request.Limit <= 0 biddersEvaluated := make([]BidderEvaluation, 0) @@ -120,30 +144,43 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { - syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) + if _, ok := biddersSeen[bidders[i]]; ok { + continue + } + syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie, request.GPPSID) biddersEvaluated = append(biddersEvaluated, evaluation) if evaluation.Status == StatusOK { syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) } + biddersSeen[bidders[i]] = struct{}{} } return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} } -func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { - syncer, exists := c.bidderSyncerLookup[bidder] +func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie, GPPSID string) (Syncer, BidderEvaluation) { + bidderNormalized, exists := c.normalizeValidBidderName(bidder) if !exists { return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} } + syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] + if !exists { + if _, ok := c.biddersKnown[bidder]; !ok { + return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + } else { + return nil, BidderEvaluation{Status: StatusUnconfiguredBidder, Bidder: bidder} + } + } + _, seen := syncersSeen[syncer.Key()] if seen { return nil, BidderEvaluation{Status: StatusDuplicate, Bidder: bidder, SyncerKey: syncer.Key()} } syncersSeen[syncer.Key()] = struct{}{} - if !syncer.SupportsType(syncTypeFilter.ForBidder(bidder)) { + if !syncer.SupportsType(syncTypeFilter.ForBidder(strings.ToLower(bidder))) { return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()} } @@ -151,12 +188,29 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} return nil, BidderEvaluation{Status: StatusAlreadySynced, Bidder: bidder, SyncerKey: syncer.Key()} } - if !privacy.GDPRAllowsBidderSync(bidder) { - return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} + userSyncActivityAllowed := privacy.ActivityAllowsUserSync(bidder) + if !userSyncActivityAllowed { + return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { + return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} } - if !privacy.CCPAAllowsBidderSync(bidder) { - return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} + if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.Enabled != nil && !*c.bidderInfo[bidder].Syncer.Enabled { + return nil, BidderEvaluation{Status: StatusBlockedByDisabledUsersync, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if privacy.GDPRInScope() && c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil && c.bidderInfo[bidder].Syncer.SkipWhen.GDPR { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil { + for _, gppSID := range c.bidderInfo[bidder].Syncer.SkipWhen.GPPSID { + if gppSID == GPPSID { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} + } + } } return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index 3b820b99f24..f48dbeff9f1 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -4,15 +4,20 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + + "github.com/prebid/prebid-server/v2/macros" ) func TestNewChooser(t *testing.T) { testCases := []struct { description string bidderSyncerLookup map[string]Syncer + bidderInfo map[string]config.BidderInfo expectedBiddersAvailable []string }{ { @@ -38,7 +43,7 @@ func TestNewChooser(t *testing.T) { } for _, test := range testCases { - chooser, _ := NewChooser(test.bidderSyncerLookup).(standardChooser) + chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{}), test.bidderInfo).(standardChooser) assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) } } @@ -47,30 +52,45 @@ func TestChooserChoose(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC} + + duplicateSyncer := fakeSyncer{key: "syncerForDuplicateTest", supportsIFrame: true} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA, "d": duplicateSyncer, "e": duplicateSyncer} + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "c": {}} + + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } + syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } cooperativeConfig := Cooperative{Enabled: true} + usersyncDisabled := ptrutil.ToPtr(false) + testCases := []struct { description string givenRequest Request givenChosenBidders []string givenCookie Cookie + givenBidderInfo map[string]config.BidderInfo + bidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expected Result }{ { description: "Cookie Opt Out", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{optOut: true}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusBlockedByUserOptOut, BiddersEvaluated: nil, @@ -80,13 +100,14 @@ func TestChooserChoose(t *testing.T) { { description: "GDPR Host Cookie Not Allowed", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ - Status: StatusBlockedByGDPR, + Status: StatusBlockedByPrivacy, BiddersEvaluated: nil, SyncersChosen: nil, }, @@ -94,11 +115,12 @@ func TestChooserChoose(t *testing.T) { { description: "No Bidders", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{}, @@ -108,11 +130,12 @@ func TestChooserChoose(t *testing.T) { { description: "One Bidder - Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -122,25 +145,42 @@ func TestChooserChoose(t *testing.T) { { description: "One Bidder - No Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"c"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, SyncersChosen: []SyncerChoice{}, }, }, + { + description: "One Bidder - No Sync - Unknown", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"unknown"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "unknown", Status: StatusUnknownBidder}}, + SyncersChosen: []SyncerChoice{}, + }, + }, { description: "Many Bidders - All Sync - Limit Disabled With 0", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, @@ -150,11 +190,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - All Sync - Limit Disabled With Negative Value", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: -1, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, @@ -164,11 +205,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Limited Sync", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 1, }, givenChosenBidders: []string{"a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -178,11 +220,12 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Limited Sync - Disqualified Syncers Don't Count Towards Limit", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 1, }, givenChosenBidders: []string{"c", "a", "b"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, @@ -192,17 +235,189 @@ func TestChooserChoose(t *testing.T) { { description: "Many Bidders - Some Sync, Some Don't", givenRequest: Request{ - Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, Limit: 0, }, givenChosenBidders: []string{"a", "c"}, givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, SyncersChosen: []SyncerChoice{syncerChoiceA}, }, }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"d", "e"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "d", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK (reverse)", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"e", "d"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "e", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Same bidder name, no duplicate warning", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a", "a"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "a", SyncerKey: fakeSyncerA.key, Status: StatusOK}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "a", Syncer: fakeSyncerA}}, + }, + }, + { + description: "Unknown Bidder", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + bidderNamesLookup: func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), false + }, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusUnknownBidder}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Case insensitive bidder name", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"AppNexus"}, + givenCookie: Cookie{}, + bidderNamesLookup: openrtb_ext.NormalizeBidderName, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, + }, + }, + { + description: "Duplicate bidder name", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"AppNexus", "appNexus"}, + givenCookie: Cookie{}, + bidderNamesLookup: openrtb_ext.NormalizeBidderName, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "appNexus", SyncerKey: "keyA", Status: StatusDuplicate}}, + SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, + }, + }, + { + description: "Disabled Usersync", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + Enabled: usersyncDisabled, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Regulation Scope GDPR", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Regulation Scope GPP", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + GPPSID: "2", + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, } bidders := []string{"anyRequested"} @@ -218,10 +433,17 @@ func TestChooserChoose(t *testing.T) { On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). Return(test.givenChosenBidders) + if test.givenBidderInfo == nil { + test.givenBidderInfo = map[string]config.BidderInfo{} + } + chooser := standardChooser{ - bidderSyncerLookup: bidderSyncerLookup, - biddersAvailable: biddersAvailable, - bidderChooser: mockBidderChooser, + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: biddersAvailable, + bidderChooser: mockBidderChooser, + normalizeValidBidderName: test.bidderNamesLookup, + biddersKnown: biddersKnown, + bidderInfo: test.givenBidderInfo, } result := chooser.Choose(test.givenRequest, &test.givenCookie) @@ -232,104 +454,256 @@ func TestChooserChoose(t *testing.T) { func TestChooserEvaluate(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB} + + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "unconfigured": {}} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "seedingAlliance": fakeSyncerA} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} - + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { + return openrtb_ext.BidderName(name), true + } cookieNeedsSync := Cookie{} - cookieAlreadyHasSyncForA := Cookie{uids: map[string]uidWithExpiry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} - cookieAlreadyHasSyncForB := Cookie{uids: map[string]uidWithExpiry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + cookieAlreadyHasSyncForA := Cookie{uids: map[string]UIDEntry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + cookieAlreadyHasSyncForB := Cookie{uids: map[string]UIDEntry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + + usersyncDisabled := ptrutil.ToPtr(false) testCases := []struct { - description string - givenBidder string - givenSyncersSeen map[string]struct{} - givenPrivacy Privacy - givenCookie Cookie - expectedSyncer Syncer - expectedEvaluation BidderEvaluation + description string + givenBidder string + normalisedBidderName string + givenSyncersSeen map[string]struct{} + givenPrivacy fakePrivacy + givenCookie Cookie + givenGPPSID string + givenBidderInfo map[string]config.BidderInfo + givenSyncTypeFilter SyncTypeFilter + normalizedBidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) + expectedSyncer Syncer + expectedEvaluation BidderEvaluation }{ { - description: "Valid", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, - }, - { - description: "Unknown Bidder", - givenBidder: "unknown", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, - }, - { - description: "Duplicate Syncer", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{"keyA": {}}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, - }, - { - description: "Incompatible Kind", - givenBidder: "b", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, - }, - { - description: "Already Synced", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieAlreadyHasSyncForA, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, - }, - { - description: "Different Bidder Already Synced", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, - givenCookie: cookieAlreadyHasSyncForB, - expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, - }, - { - description: "Blocked By GDPR", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, - }, - { - description: "Blocked By CCPA", - givenBidder: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false}, - givenCookie: cookieNeedsSync, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + description: "Valid", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, + }, + { + description: "Unknown Bidder", + givenBidder: "unknown", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, + }, + { + description: "Duplicate Syncer", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{"keyA": {}}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, + }, + { + description: "Incompatible Kind", + givenBidder: "b", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, + }, + { + description: "Already Synced", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieAlreadyHasSyncForA, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, + }, + { + description: "Different Bidder Already Synced", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieAlreadyHasSyncForB, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, + }, + { + description: "Blocked By GDPR", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, + }, + { + description: "Blocked By activity control", + givenBidder: "a", + normalisedBidderName: "", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: false}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, + }, + { + description: "Case insensitive bidder name", + givenBidder: "AppNexus", + normalisedBidderName: "appnexus", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, + }, + { + description: "Case insensitivity check for sync type filter", + givenBidder: "SeedingAlliance", + normalisedBidderName: "seedingAlliance", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: SyncTypeFilter{ + IFrame: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeInclude), + Redirect: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeExclude), + }, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: fakeSyncerA, + expectedEvaluation: BidderEvaluation{Bidder: "SeedingAlliance", SyncerKey: "keyA", Status: StatusOK}, + }, + { + description: "Case Insensitivity Check For Blocked By GDPR", + givenBidder: "AppNexus", + normalisedBidderName: "appnexus", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, + }, + { + description: "Unconfigured Bidder", + givenBidder: "unconfigured", + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "unconfigured", Status: StatusUnconfiguredBidder}, + }, + { + description: "Disabled Usersync", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + Enabled: usersyncDisabled, + }, + }, + }, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}, + }, + { + description: "Blocked By Regulation Scope - GDPR", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, + }, + { + description: "Blocked By Regulation Scope - GPP", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + givenGPPSID: "2", + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, }, } for _, test := range testCases { - chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) - sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, syncTypeFilter, test.givenPrivacy, &test.givenCookie) + t.Run(test.description, func(t *testing.T) { + chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown, test.givenBidderInfo).(standardChooser) + chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup + sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie, test.givenGPPSID) - assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") - assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") + assert.Equal(t, test.normalisedBidderName, test.givenPrivacy.inputBidderName) + assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") + assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") + }) } } @@ -346,13 +720,14 @@ type fakeSyncer struct { key string supportsIFrame bool supportsRedirect bool + formatOverride string } func (s fakeSyncer) Key() string { return s.key } -func (s fakeSyncer) DefaultSyncType() SyncType { +func (s fakeSyncer) DefaultResponseFormat() SyncType { return SyncTypeIFrame } @@ -368,24 +743,37 @@ func (s fakeSyncer) SupportsType(syncTypes []SyncType) bool { return false } -func (fakeSyncer) GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) { +func (fakeSyncer) GetSync([]SyncType, macros.UserSyncPrivacy) (Sync, error) { return Sync{}, nil } type fakePrivacy struct { - gdprAllowsHostCookie bool - gdprAllowsBidderSync bool - ccpaAllowsBidderSync bool + gdprAllowsHostCookie bool + gdprAllowsBidderSync bool + ccpaAllowsBidderSync bool + activityAllowUserSync bool + gdprInScope bool + inputBidderName string } -func (p fakePrivacy) GDPRAllowsHostCookie() bool { +func (p *fakePrivacy) GDPRAllowsHostCookie() bool { return p.gdprAllowsHostCookie } -func (p fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { +func (p *fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { + p.inputBidderName = bidder return p.gdprAllowsBidderSync } -func (p fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { +func (p *fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { + p.inputBidderName = bidder return p.ccpaAllowsBidderSync } + +func (p *fakePrivacy) ActivityAllowsUserSync(bidder string) bool { + return p.activityAllowUserSync +} + +func (p *fakePrivacy) GDPRInScope() bool { + return p.gdprInScope +} diff --git a/usersync/cookie.go b/usersync/cookie.go index 4f80fa5d5f6..b4bc821c9d7 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -1,15 +1,13 @@ package usersync import ( - "encoding/base64" - "encoding/json" "errors" - "math" "net/http" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) const uidCookieName = "uids" @@ -20,72 +18,140 @@ const uidTTL = 14 * 24 * time.Hour // Cookie is the cookie used in Prebid Server. // -// To get an instance of this from a request, use ParseCookieFromRequest. -// To write an instance onto a response, use SetCookieOnResponse. +// To get an instance of this from a request, use ReadCookie. +// To write an instance onto a response, use WriteCookie. type Cookie struct { - uids map[string]uidWithExpiry + uids map[string]UIDEntry optOut bool } -// uidWithExpiry bundles the UID with an Expiration date. -// After the expiration, the UID is no longer valid. -type uidWithExpiry struct { +// UIDEntry bundles the UID with an Expiration date. +type UIDEntry struct { // UID is the ID given to a user by a particular bidder UID string `json:"uid"` // Expires is the time at which this UID should no longer apply. Expires time.Time `json:"expires"` } -// ParseCookieFromRequest parses the UserSyncMap from an HTTP Request. -func ParseCookieFromRequest(r *http.Request, cookie *config.HostCookie) *Cookie { - if cookie.OptOutCookie.Name != "" { - optOutCookie, err1 := r.Cookie(cookie.OptOutCookie.Name) - if err1 == nil && optOutCookie.Value == cookie.OptOutCookie.Value { - pc := NewCookie() - pc.SetOptOut(true) - return pc - } +// NewCookie returns a new empty cookie. +func NewCookie() *Cookie { + return &Cookie{ + uids: make(map[string]UIDEntry), } - var parsed *Cookie - uidCookie, err2 := r.Cookie(uidCookieName) - if err2 == nil { - parsed = ParseCookie(uidCookie) - } else { - parsed = NewCookie() +} + +// ReadCookie reads the cookie from the request +func ReadCookie(r *http.Request, decoder Decoder, host *config.HostCookie) *Cookie { + if hostOptOutCookie := checkHostCookieOptOut(r, host); hostOptOutCookie != nil { + return hostOptOutCookie + } + + // Read cookie from request + cookieFromRequest, err := r.Cookie(uidCookieName) + if err != nil { + return NewCookie() } - // Fixes #582 - if uid, _, _ := parsed.GetUID(cookie.Family); uid == "" && cookie.CookieName != "" { - if hostCookie, err := r.Cookie(cookie.CookieName); err == nil { - parsed.TrySync(cookie.Family, hostCookie.Value) + decodedCookie := decoder.Decode(cookieFromRequest.Value) + + return decodedCookie +} + +// PrepareCookieForWrite ejects UIDs as long as the cookie is too full +func (cookie *Cookie) PrepareCookieForWrite(cfg *config.HostCookie, encoder Encoder, ejector Ejector) (string, error) { + for len(cookie.uids) > 0 { + encodedCookie, err := encoder.Encode(cookie) + if err != nil { + return encodedCookie, nil } + + // Convert to HTTP Cookie to Get Size + httpCookie := &http.Cookie{ + Name: uidCookieName, + Value: encodedCookie, + Expires: time.Now().Add(cfg.TTLDuration()), + Path: "/", + } + cookieSize := len([]byte(httpCookie.String())) + + isCookieTooBig := cookieSize > cfg.MaxCookieSizeBytes && cfg.MaxCookieSizeBytes > 0 + if !isCookieTooBig { + return encodedCookie, nil + } else if len(cookie.uids) == 1 { + return "", errors.New("uid that's trying to be synced is bigger than MaxCookieSize") + } + + uidToDelete, err := ejector.Choose(cookie.uids) + if err != nil { + return encodedCookie, err + } + delete(cookie.uids, uidToDelete) } - return parsed + return "", nil } -// ParseCookie parses the UserSync cookie from a raw HTTP cookie. -func ParseCookie(httpCookie *http.Cookie) *Cookie { - jsonValue, err := base64.URLEncoding.DecodeString(httpCookie.Value) - if err != nil { - // corrupted cookie; we should reset - return NewCookie() +// WriteCookie sets the prepared cookie onto the header +func WriteCookie(w http.ResponseWriter, encodedCookie string, cfg *config.HostCookie, setSiteCookie bool) { + ttl := cfg.TTLDuration() + + httpCookie := &http.Cookie{ + Name: uidCookieName, + Value: encodedCookie, + Expires: time.Now().Add(ttl), + Path: "/", } - var cookie Cookie - if err = json.Unmarshal(jsonValue, &cookie); err != nil { - // corrupted cookie; we should reset - return NewCookie() + if cfg.Domain != "" { + httpCookie.Domain = cfg.Domain } - return &cookie + if setSiteCookie { + httpCookie.Secure = true + httpCookie.SameSite = http.SameSiteNoneMode + } + + w.Header().Add("Set-Cookie", httpCookie.String()) } -// NewCookie returns a new empty cookie. -func NewCookie() *Cookie { - return &Cookie{ - uids: make(map[string]uidWithExpiry), +// Sync tries to set the UID for some syncer key. It returns an error if the set didn't happen. +func (cookie *Cookie) Sync(key string, uid string) error { + if !cookie.AllowSyncs() { + return errors.New("the user has opted out of prebid server cookie syncs") + } + + if checkAudienceNetwork(key, uid) { + return errors.New("audienceNetwork uses a UID of 0 as \"not yet recognized\"") + } + + // Sync + cookie.uids[key] = UIDEntry{ + UID: uid, + Expires: time.Now().Add(uidTTL), + } + + return nil +} + +// SyncHostCookie syncs the request cookie with the host cookie +func SyncHostCookie(r *http.Request, requestCookie *Cookie, host *config.HostCookie) { + if uid, _, _ := requestCookie.GetUID(host.Family); uid == "" && host.CookieName != "" { + if hostCookie, err := r.Cookie(host.CookieName); err == nil { + requestCookie.Sync(host.Family, hostCookie.Value) + } } } +func checkHostCookieOptOut(r *http.Request, host *config.HostCookie) *Cookie { + if host.OptOutCookie.Name != "" { + optOutCookie, err := r.Cookie(host.OptOutCookie.Name) + if err == nil && optOutCookie.Value == host.OptOutCookie.Value { + hostOptOut := NewCookie() + hostOptOut.SetOptOut(true) + return hostOptOut + } + } + return nil +} + // AllowSyncs is true if the user lets bidders sync cookies, and false otherwise. func (cookie *Cookie) AllowSyncs() bool { return cookie != nil && !cookie.optOut @@ -96,30 +162,12 @@ func (cookie *Cookie) SetOptOut(optOut bool) { cookie.optOut = optOut if optOut { - cookie.uids = make(map[string]uidWithExpiry) - } -} - -// Gets an HTTP cookie containing all the data from this UserSyncMap. This is a snapshot--not a live view. -func (cookie *Cookie) ToHTTPCookie(ttl time.Duration) *http.Cookie { - j, _ := json.Marshal(cookie) - b64 := base64.URLEncoding.EncodeToString(j) - - return &http.Cookie{ - Name: uidCookieName, - Value: b64, - Expires: time.Now().Add(ttl), - Path: "/", + cookie.uids = make(map[string]UIDEntry) } } // GetUID Gets this user's ID for the given syncer key. -// The first returned value is the user's ID. -// The second returned value is true if we had a value stored, and false if we didn't. -// The third returned value is true if that value is "active", and false if it's expired. -// -// If no value was stored, then the "isActive" return value will be false. -func (cookie *Cookie) GetUID(key string) (string, bool, bool) { +func (cookie *Cookie) GetUID(key string) (uid string, isUIDFound bool, isUIDActive bool) { if cookie != nil { if uid, ok := cookie.uids[key]; ok { return uid.UID, true, time.Now().Before(uid.Expires) @@ -140,41 +188,6 @@ func (cookie *Cookie) GetUIDs() map[string]string { return uids } -// SetCookieOnResponse is a shortcut for "ToHTTPCookie(); cookie.setDomain(domain); setCookie(w, cookie)" -func (cookie *Cookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCookie bool, cfg *config.HostCookie, ttl time.Duration) { - httpCookie := cookie.ToHTTPCookie(ttl) - var domain string = cfg.Domain - - if domain != "" { - httpCookie.Domain = domain - } - - var currSize int = len([]byte(httpCookie.String())) - for cfg.MaxCookieSizeBytes > 0 && currSize > cfg.MaxCookieSizeBytes && len(cookie.uids) > 0 { - var oldestElem string = "" - var oldestDate int64 = math.MaxInt64 - for key, value := range cookie.uids { - timeUntilExpiration := time.Until(value.Expires) - if timeUntilExpiration < time.Duration(oldestDate) { - oldestElem = key - oldestDate = int64(timeUntilExpiration) - } - } - delete(cookie.uids, oldestElem) - httpCookie = cookie.ToHTTPCookie(ttl) - if domain != "" { - httpCookie.Domain = domain - } - currSize = len([]byte(httpCookie.String())) - } - - if setSiteCookie { - httpCookie.Secure = true - httpCookie.SameSite = http.SameSiteNoneMode - } - w.Header().Add("Set-Cookie", httpCookie.String()) -} - // Unsync removes the user's ID for the given syncer key from this cookie. func (cookie *Cookie) Unsync(key string) { delete(cookie.uids, key) @@ -199,24 +212,8 @@ func (cookie *Cookie) HasAnyLiveSyncs() bool { return false } -// TrySync tries to set the UID for some syncer key. It returns an error if the set didn't happen. -func (cookie *Cookie) TrySync(key string, uid string) error { - if !cookie.AllowSyncs() { - return errors.New("The user has opted out of prebid server cookie syncs.") - } - - // At the moment, Facebook calls /setuid with a UID of 0 if the user isn't logged into Facebook. - // They shouldn't be sending us a sentinel value... but since they are, we're refusing to save that ID. - if key == string(openrtb_ext.BidderAudienceNetwork) && uid == "0" { - return errors.New("audienceNetwork uses a UID of 0 as \"not yet recognized\".") - } - - cookie.uids[key] = uidWithExpiry{ - UID: uid, - Expires: time.Now().Add(uidTTL), - } - - return nil +func checkAudienceNetwork(key string, uid string) bool { + return key == string(openrtb_ext.BidderAudienceNetwork) && uid == "0" } // cookieJson defines the JSON contract for the cookie data's storage format. @@ -224,12 +221,12 @@ func (cookie *Cookie) TrySync(key string, uid string) error { // This exists so that Cookie (which is public) can have private fields, and the rest of // the code doesn't have to worry about the cookie data storage format. type cookieJson struct { - UIDs map[string]uidWithExpiry `json:"tempUIDs,omitempty"` - OptOut bool `json:"optout,omitempty"` + UIDs map[string]UIDEntry `json:"tempUIDs,omitempty"` + OptOut bool `json:"optout,omitempty"` } func (cookie *Cookie) MarshalJSON() ([]byte, error) { - return json.Marshal(cookieJson{ + return jsonutil.Marshal(cookieJson{ UIDs: cookie.uids, OptOut: cookie.optOut, }) @@ -237,7 +234,7 @@ func (cookie *Cookie) MarshalJSON() ([]byte, error) { func (cookie *Cookie) UnmarshalJSON(b []byte) error { var cookieContract cookieJson - if err := json.Unmarshal(b, &cookieContract); err != nil { + if err := jsonutil.Unmarshal(b, &cookieContract); err != nil { return err } @@ -250,10 +247,10 @@ func (cookie *Cookie) UnmarshalJSON(b []byte) error { } if cookie.uids == nil { - cookie.uids = make(map[string]uidWithExpiry) + cookie.uids = make(map[string]UIDEntry) } - // Audience Network / Facebook Handling + // Audience Network Handling if id, ok := cookie.uids[string(openrtb_ext.BidderAudienceNetwork)]; ok && id.UID == "0" { delete(cookie.uids, string(openrtb_ext.BidderAudienceNetwork)) } diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index ee5062ea152..1ecfe51b5b5 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -1,177 +1,670 @@ package usersync import ( - "encoding/base64" + "errors" "net/http" "net/http/httptest" - "strings" "testing" "time" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestOptOutCookie(t *testing.T) { - cookie := &Cookie{ - uids: map[string]uidWithExpiry{"appnexus": {UID: "test"}}, - optOut: true, +func TestReadCookie(t *testing.T) { + testCases := []struct { + name string + givenRequest *http.Request + givenHttpCookie *http.Cookie + givenCookie *Cookie + givenDecoder Decoder + expectedCookie *Cookie + }{ + { + name: "simple-cookie", + givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil), + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + }, + }, + optOut: false, + }, + }, + { + name: "empty-cookie", + givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil), + givenCookie: &Cookie{}, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: false, + }, + }, + { + name: "nil-cookie", + givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil), + givenCookie: nil, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: false, + }, + }, + { + name: "corruptted-http-cookie", + givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil), + givenHttpCookie: &http.Cookie{ + Name: "uids", + Value: "bad base64 encoding", + }, + givenCookie: nil, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: false, + }, + }, } - ensureConsistency(t, cookie) -} -func TestEmptyOptOutCookie(t *testing.T) { - cookie := &Cookie{ - uids: make(map[string]uidWithExpiry), - optOut: true, + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if test.givenCookie != nil { + httpCookie, err := ToHTTPCookie(test.givenCookie) + assert.NoError(t, err) + test.givenRequest.AddCookie(httpCookie) + } else if test.givenCookie == nil && test.givenHttpCookie != nil { + test.givenRequest.AddCookie(test.givenHttpCookie) + } + actualCookie := ReadCookie(test.givenRequest, Base64Decoder{}, &config.HostCookie{}) + assert.Equal(t, test.expectedCookie.uids, actualCookie.uids) + assert.Equal(t, test.expectedCookie.optOut, actualCookie.optOut) + }) } - ensureConsistency(t, cookie) } -func TestEmptyCookie(t *testing.T) { - cookie := &Cookie{ - uids: make(map[string]uidWithExpiry), - optOut: false, +func TestWriteCookie(t *testing.T) { + encoder := Base64Encoder{} + decoder := Base64Decoder{} + + testCases := []struct { + name string + givenCookie *Cookie + givenSetSiteCookie bool + expectedCookie *Cookie + }{ + { + name: "simple-cookie", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + givenSetSiteCookie: false, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + }, + { + name: "simple-cookie-opt-out", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: true, + }, + givenSetSiteCookie: true, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: true, + }, + }, + { + name: "cookie-multiple-uids", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + "rubicon": { + UID: "UID2", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + givenSetSiteCookie: true, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + "rubicon": { + UID: "UID2", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + }, } - ensureConsistency(t, cookie) -} -func TestCookieWithData(t *testing.T) { - cookie := newSampleCookie() - ensureConsistency(t, cookie) + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + // Write Cookie + w := httptest.NewRecorder() + encodedCookie, err := encoder.Encode(test.givenCookie) + assert.NoError(t, err) + WriteCookie(w, encodedCookie, &config.HostCookie{}, test.givenSetSiteCookie) + writtenCookie := w.Header().Get("Set-Cookie") + + // Read Cookie + header := http.Header{} + header.Add("Cookie", writtenCookie) + r := &http.Request{Header: header} + actualCookie := ReadCookie(r, decoder, &config.HostCookie{}) + + assert.Equal(t, test.expectedCookie, actualCookie) + }) + } } -func TestBidderNameGets(t *testing.T) { - cookie := newSampleCookie() - id, exists, _ := cookie.GetUID("adnxs") - if !exists { - t.Errorf("Cookie missing expected Appnexus ID") - } - if id != "123" { - t.Errorf("Bad appnexus id. Expected %s, got %s", "123", id) +func TestSync(t *testing.T) { + testCases := []struct { + name string + givenCookie *Cookie + givenSyncerKey string + givenUID string + expectedCookie *Cookie + expectedError error + }{ + { + name: "simple-sync", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{}, + }, + givenSyncerKey: "adnxs", + givenUID: "123", + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "123", + }, + }, + }, + }, + { + name: "dont-allow-syncs", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: true, + }, + givenSyncerKey: "adnxs", + givenUID: "123", + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + }, + expectedError: errors.New("the user has opted out of prebid server cookie syncs"), + }, + { + name: "audienceNetwork", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{}, + }, + givenSyncerKey: string(openrtb_ext.BidderAudienceNetwork), + givenUID: "0", + expectedError: errors.New("audienceNetwork uses a UID of 0 as \"not yet recognized\""), + }, } - id, exists, _ = cookie.GetUID("rubicon") - if !exists { - t.Errorf("Cookie missing expected Rubicon ID") - } - if id != "456" { - t.Errorf("Bad rubicon id. Expected %s, got %s", "456", id) + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + err := test.givenCookie.Sync(test.givenSyncerKey, test.givenUID) + if test.expectedError != nil { + assert.Equal(t, test.expectedError, err) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expectedCookie.uids[test.givenSyncerKey].UID, test.givenCookie.uids[test.givenSyncerKey].UID) + } + }) } } -func TestRejectAudienceNetworkCookie(t *testing.T) { - raw := &Cookie{ - uids: map[string]uidWithExpiry{ - "audienceNetwork": newTempId("0", 10), +func TestGetUIDs(t *testing.T) { + testCases := []struct { + name string + givenCookie *Cookie + expectedCookie *Cookie + expectedLen int + }{ + { + name: "two-uids", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "123", + }, + "rubicon": { + UID: "456", + }, + }, + }, + expectedLen: 2, + }, + { + name: "one-uid", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "123", + }, + }, + }, + expectedLen: 1, + }, + { + name: "empty", + givenCookie: &Cookie{}, + expectedLen: 0, + }, + { + name: "nil", + givenCookie: nil, + expectedLen: 0, }, - optOut: false, } - parsed := ParseCookie(raw.ToHTTPCookie(90 * 24 * time.Hour)) - if parsed.HasLiveSync("audienceNetwork") { - t.Errorf("Cookie serializing and deserializing should delete audienceNetwork values of 0") + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + uids := test.givenCookie.GetUIDs() + assert.Len(t, uids, test.expectedLen) + for key, value := range uids { + assert.Equal(t, test.givenCookie.uids[key].UID, value) + } + + }) } +} + +func TestWriteCookieUserAgent(t *testing.T) { + encoder := Base64Encoder{} - err := parsed.TrySync("audienceNetwork", "0") - if err == nil { - t.Errorf("Cookie should reject audienceNetwork values of 0.") + testCases := []struct { + name string + givenUserAgent string + givenCookie *Cookie + givenHostCookie config.HostCookie + givenSetSiteCookie bool + expectedContains string + expectedNotContains string + }{ + { + name: "same-site-none", + givenUserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + givenHostCookie: config.HostCookie{}, + givenSetSiteCookie: true, + expectedContains: "; Secure;", + }, + { + name: "older-chrome-version", + givenUserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3770.142 Safari/537.36", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + givenHostCookie: config.HostCookie{}, + givenSetSiteCookie: true, + expectedNotContains: "SameSite=none", + }, } - if parsed.HasLiveSync("audienceNetwork") { - t.Errorf("Cookie The cookie should have rejected the audienceNetwork sync.") + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + // Set Up + req := httptest.NewRequest("GET", "http://www.prebid.com", nil) + req.Header.Set("User-Agent", test.givenUserAgent) + + // Write Cookie + w := httptest.NewRecorder() + encodedCookie, err := encoder.Encode(test.givenCookie) + assert.NoError(t, err) + WriteCookie(w, encodedCookie, &test.givenHostCookie, test.givenSetSiteCookie) + writtenCookie := w.Header().Get("Set-Cookie") + + if test.expectedContains == "" { + assert.NotContains(t, writtenCookie, test.expectedNotContains) + } else { + assert.Contains(t, writtenCookie, test.expectedContains) + } + }) } } -func TestOptOutReset(t *testing.T) { - cookie := newSampleCookie() +func TestPrepareCookieForWrite(t *testing.T) { + encoder := Base64Encoder{} + decoder := Base64Decoder{} + + mainCookie := &Cookie{ + uids: map[string]UIDEntry{ + "mainUID": newTempId("1234567890123456789012345678901234567890123456", 7), + "2": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6), + "3": newTempId("123456789012345678901234567896123456789012345678", 5), + "4": newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4), + "5": newTempId("12345678901234567890123456789012345678901234567890", 3), + "6": newTempId("abcdefghij", 2), + "7": newTempId("abcdefghijklmnopqrstuvwxy", 1), + }, + optOut: false, + } - cookie.SetOptOut(true) - if cookie.AllowSyncs() { - t.Error("After SetOptOut(true), a cookie should not allow more user syncs.") + errorCookie := &Cookie{ + uids: map[string]UIDEntry{ + "syncerNotPriority": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 7), + "2": newTempId("1234567890123456789012345678901234567890123456", 7), // Priority Element + }, + optOut: false, } - ensureConsistency(t, cookie) -} -func TestOptIn(t *testing.T) { - cookie := &Cookie{ - uids: make(map[string]uidWithExpiry), - optOut: true, + ejector := &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"mainUID"}, + {"2", "3"}, + {"4", "5", "6"}, + {"7"}, + }, + SyncersByBidder: map[string]Syncer{ + "mainUID": fakeSyncer{ + key: "mainUID", + }, + "2": fakeSyncer{ + key: "2", + }, + "3": fakeSyncer{ + key: "3", + }, + "4": fakeSyncer{ + key: "4", + }, + "5": fakeSyncer{ + key: "5", + }, + "6": fakeSyncer{ + key: "6", + }, + "mistmatchedBidder": fakeSyncer{ + key: "7", + }, + }, + TieEjector: &OldestEjector{}, } - cookie.SetOptOut(false) - if !cookie.AllowSyncs() { - t.Error("After SetOptOut(false), a cookie should allow more user syncs.") + testCases := []struct { + name string + givenMaxCookieSize int + givenCookieToSend *Cookie + givenIsSyncerPriority bool + expectedRemainingUidKeys []string + expectedError error + }{ + { + name: "no-uids-ejected", + givenMaxCookieSize: 2000, + givenCookieToSend: mainCookie, + givenIsSyncerPriority: true, + expectedRemainingUidKeys: []string{ + "mainUID", "2", "3", "4", "5", "6", "7", + }, + }, + { + name: "invalid-max-size", + givenMaxCookieSize: -100, + givenCookieToSend: mainCookie, + expectedRemainingUidKeys: []string{ + "mainUID", "2", "3", "4", "5", "6", "7", + }, + }, + { + name: "syncer-is-not-priority", + givenMaxCookieSize: 100, + givenCookieToSend: errorCookie, + givenIsSyncerPriority: false, + expectedError: errors.New("syncer key is not a priority, and there are only priority elements left"), + }, + { + name: "no-uids-ejected-2", + givenMaxCookieSize: 0, + givenCookieToSend: mainCookie, + givenIsSyncerPriority: true, + expectedRemainingUidKeys: []string{ + "mainUID", "2", "3", "4", "5", "6", "7", + }, + }, + { + name: "one-uid-ejected", + givenMaxCookieSize: 900, + givenCookieToSend: mainCookie, + givenIsSyncerPriority: true, + expectedRemainingUidKeys: []string{ + "mainUID", "2", "3", "4", "5", "6", + }, + }, + { + name: "four-uids-ejected", + givenMaxCookieSize: 500, + givenCookieToSend: mainCookie, + givenIsSyncerPriority: true, + expectedRemainingUidKeys: []string{ + "mainUID", "2", "3", + }, + }, + { + name: "all-but-one-uids-ejected", + givenMaxCookieSize: 300, + givenCookieToSend: mainCookie, + givenIsSyncerPriority: true, + expectedRemainingUidKeys: []string{ + "mainUID", + }, + }, + { + name: "only-main-uid-left", + givenMaxCookieSize: 100, + givenCookieToSend: mainCookie, + expectedError: errors.New("uid that's trying to be synced is bigger than MaxCookieSize"), + expectedRemainingUidKeys: []string{}, + }, } - ensureConsistency(t, cookie) -} -func TestParseCorruptedCookie(t *testing.T) { - raw := http.Cookie{ - Name: "uids", - Value: "bad base64 encoding", + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + ejector.IsSyncerPriority = test.givenIsSyncerPriority + encodedCookie, err := test.givenCookieToSend.PrepareCookieForWrite(&config.HostCookie{MaxCookieSizeBytes: test.givenMaxCookieSize}, encoder, ejector) + + if test.expectedError != nil { + assert.Equal(t, test.expectedError, err) + } else { + assert.NoError(t, err) + decodedCookie := decoder.Decode(encodedCookie) + + for _, key := range test.expectedRemainingUidKeys { + _, ok := decodedCookie.uids[key] + assert.Equal(t, true, ok) + } + assert.Equal(t, len(decodedCookie.uids), len(test.expectedRemainingUidKeys)) + } + }) } - parsed := ParseCookie(&raw) - ensureEmptyMap(t, parsed) } -func TestParseCorruptedCookieJSON(t *testing.T) { - cookieData := base64.URLEncoding.EncodeToString([]byte("bad json")) - raw := http.Cookie{ - Name: "uids", - Value: cookieData, +func TestSyncHostCookie(t *testing.T) { + testCases := []struct { + name string + givenCookie *Cookie + givenUID string + givenHostCookie *config.HostCookie + expectedCookie *Cookie + expectedError error + }{ + { + name: "simple-sync", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{}, + }, + givenHostCookie: &config.HostCookie{ + Family: "syncer", + CookieName: "adnxs", + }, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "syncer": { + UID: "some-user-id", + }, + }, + }, + }, + { + name: "uids-already-present", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "some-syncer": { + UID: "some-other-user-id", + }, + }, + }, + givenHostCookie: &config.HostCookie{ + Family: "syncer", + CookieName: "adnxs", + }, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "syncer": { + UID: "some-user-id", + }, + "some-syncer": { + UID: "some-other-user-id", + }, + }, + }, + }, + { + name: "host-already-synced", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "syncer": { + UID: "some-user-id", + }, + }, + }, + givenHostCookie: &config.HostCookie{ + Family: "syncer", + CookieName: "adnxs", + }, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "syncer": { + UID: "some-user-id", + }, + }, + }, + }, } - parsed := ParseCookie(&raw) - ensureEmptyMap(t, parsed) -} -func TestParseNilSyncMap(t *testing.T) { - raw := http.Cookie{ - Name: uidCookieName, - Value: "", + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + r := httptest.NewRequest("POST", "http://www.prebid.com", nil) + r.AddCookie(&http.Cookie{ + Name: test.givenHostCookie.CookieName, + Value: "some-user-id", + }) + + SyncHostCookie(r, test.givenCookie, test.givenHostCookie) + for key, value := range test.expectedCookie.uids { + assert.Equal(t, value.UID, test.givenCookie.uids[key].UID) + } + }) } - parsed := ParseCookie(&raw) - ensureEmptyMap(t, parsed) - ensureConsistency(t, parsed) } -func TestParseOtherCookie(t *testing.T) { - req := httptest.NewRequest("POST", "http://www.prebid.com", nil) - otherCookieName := "other" - id := "some-user-id" - req.AddCookie(&http.Cookie{ - Name: otherCookieName, - Value: id, - }) - parsed := ParseCookieFromRequest(req, &config.HostCookie{ - Family: "adnxs", - CookieName: otherCookieName, - }) - val, _, _ := parsed.GetUID("adnxs") - if val != id { - t.Errorf("Bad cookie value. Expected %s, got %s", id, val) +func TestBidderNameGets(t *testing.T) { + cookie := newSampleCookie() + id, exists, _ := cookie.GetUID("adnxs") + if !exists { + t.Errorf("Cookie missing expected Appnexus ID") + } + if id != "123" { + t.Errorf("Bad appnexus id. Expected %s, got %s", "123", id) + } + + id, exists, _ = cookie.GetUID("rubicon") + if !exists { + t.Errorf("Cookie missing expected Rubicon ID") + } + if id != "456" { + t.Errorf("Bad rubicon id. Expected %s, got %s", "456", id) } } -func TestParseCookieFromRequestOptOut(t *testing.T) { +func TestReadCookieOptOut(t *testing.T) { optOutCookieName := "optOutCookieName" optOutCookieValue := "optOutCookieValue" + decoder := Base64Decoder{} - existingCookie := *(&Cookie{ - uids: map[string]uidWithExpiry{ + cookie := *(&Cookie{ + uids: map[string]UIDEntry{ "foo": newTempId("fooID", 1), "bar": newTempId("barID", 2), }, optOut: false, - }).ToHTTPCookie(24 * time.Hour) + }) + + existingCookie, _ := ToHTTPCookie(&cookie) testCases := []struct { description string - givenExistingCookies []http.Cookie + givenExistingCookies []*http.Cookie expectedEmpty bool expectedSetOptOut bool }{ { description: "Opt Out Cookie", - givenExistingCookies: []http.Cookie{ + givenExistingCookies: []*http.Cookie{ existingCookie, {Name: optOutCookieName, Value: optOutCookieValue}}, expectedEmpty: true, @@ -179,14 +672,14 @@ func TestParseCookieFromRequestOptOut(t *testing.T) { }, { description: "No Opt Out Cookie", - givenExistingCookies: []http.Cookie{ + givenExistingCookies: []*http.Cookie{ existingCookie}, expectedEmpty: false, expectedSetOptOut: false, }, { description: "Opt Out Cookie - Wrong Value", - givenExistingCookies: []http.Cookie{ + givenExistingCookies: []*http.Cookie{ existingCookie, {Name: optOutCookieName, Value: "wrong"}}, expectedEmpty: false, @@ -194,7 +687,7 @@ func TestParseCookieFromRequestOptOut(t *testing.T) { }, { description: "Opt Out Cookie - Wrong Name", - givenExistingCookies: []http.Cookie{ + givenExistingCookies: []*http.Cookie{ existingCookie, {Name: "wrong", Value: optOutCookieValue}}, expectedEmpty: false, @@ -202,7 +695,7 @@ func TestParseCookieFromRequestOptOut(t *testing.T) { }, { description: "Opt Out Cookie - No Host Cookies", - givenExistingCookies: []http.Cookie{ + givenExistingCookies: []*http.Cookie{ {Name: optOutCookieName, Value: optOutCookieValue}}, expectedEmpty: true, expectedSetOptOut: true, @@ -213,10 +706,10 @@ func TestParseCookieFromRequestOptOut(t *testing.T) { req := httptest.NewRequest("POST", "http://www.prebid.com", nil) for _, c := range test.givenExistingCookies { - req.AddCookie(&c) + req.AddCookie(c) } - parsed := ParseCookieFromRequest(req, &config.HostCookie{ + parsed := ReadCookie(req, decoder, &config.HostCookie{ Family: "foo", OptOutCookie: config.Cookie{ Name: optOutCookieName, @@ -233,125 +726,59 @@ func TestParseCookieFromRequestOptOut(t *testing.T) { } } -func TestCookieReadWrite(t *testing.T) { - cookie := newSampleCookie() - - received := writeThenRead(cookie, 0) - uid, exists, isLive := received.GetUID("adnxs") - if !exists || !isLive || uid != "123" { - t.Errorf("Received cookie should have the adnxs ID=123. Got %s", uid) - } - - uid, exists, isLive = received.GetUID("rubicon") - if !exists || !isLive || uid != "456" { - t.Errorf("Received cookie should have the rubicon ID=456. Got %s", uid) - } - - assert.True(t, received.HasAnyLiveSyncs(), "Has Live Syncs") - assert.Len(t, received.uids, 2, "Sync Count") -} - -func TestNilCookie(t *testing.T) { - var nilCookie *Cookie - - if nilCookie.HasLiveSync("anything") { - t.Error("nil cookies should respond with false when asked if they have a sync") - } - - if nilCookie.HasAnyLiveSyncs() { - t.Error("nil cookies shouldn't have any syncs.") - } - - if nilCookie.AllowSyncs() { - t.Error("nil cookies shouldn't allow syncs to take place.") +func TestOptIn(t *testing.T) { + cookie := &Cookie{ + uids: make(map[string]UIDEntry), + optOut: true, } - uid, hadUID, isLive := nilCookie.GetUID("anything") - - if uid != "" { - t.Error("nil cookies should return empty strings for the UID.") - } - if hadUID { - t.Error("nil cookies shouldn't claim to have a UID mapping.") - } - if isLive { - t.Error("nil cookies shouldn't report live UID mappings.") + cookie.SetOptOut(false) + if !cookie.AllowSyncs() { + t.Error("After SetOptOut(false), a cookie should allow more user syncs.") } + ensureConsistency(t, cookie) } -func TestGetUIDs(t *testing.T) { +func TestOptOutReset(t *testing.T) { cookie := newSampleCookie() - uids := cookie.GetUIDs() - assert.Len(t, uids, 2, "GetUIDs should return user IDs for all bidders") - assert.Equal(t, "123", uids["adnxs"], "GetUIDs should return the correct user ID for each bidder") - assert.Equal(t, "456", uids["rubicon"], "GetUIDs should return the correct user ID for each bidder") + cookie.SetOptOut(true) + if cookie.AllowSyncs() { + t.Error("After SetOptOut(true), a cookie should not allow more user syncs.") + } + ensureConsistency(t, cookie) } -func TestGetUIDsWithEmptyCookie(t *testing.T) { - cookie := &Cookie{} - uids := cookie.GetUIDs() - - assert.Len(t, uids, 0, "GetUIDs shouldn't return any user syncs for an empty cookie") +func TestOptOutCookie(t *testing.T) { + cookie := &Cookie{ + uids: make(map[string]UIDEntry), + optOut: true, + } + ensureConsistency(t, cookie) } -func TestGetUIDsWithNilCookie(t *testing.T) { - var cookie *Cookie - uids := cookie.GetUIDs() - - assert.Len(t, uids, 0, "GetUIDs shouldn't return any user syncs for a nil cookie") +func newTempId(uid string, offset int) UIDEntry { + return UIDEntry{ + UID: uid, + Expires: time.Now().Add(time.Duration(offset) * time.Minute).UTC(), + } } -func TestTrimCookiesClosestExpirationDates(t *testing.T) { - cookieToSend := &Cookie{ - uids: map[string]uidWithExpiry{ - "k1": newTempId("12345678901234567890123456789012345678901234567890", 7), - "k2": newTempId("abcdefghijklmnopqrstuvwxyz", 1), - "k3": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6), - "k4": newTempId("12345678901234567890123456789612345678901234567890", 5), - "k5": newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4), - "k6": newTempId("12345678901234567890123456789012345678901234567890", 3), - "k7": newTempId("abcdefghijklmnopqrstuvwxyz", 2), +func newSampleCookie() *Cookie { + return &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": newTempId("123", 10), + "rubicon": newTempId("456", 10), }, optOut: false, } - - type aTest struct { - maxCookieSize int - expKeys []string - } - testCases := []aTest{ - {maxCookieSize: 2000, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //1 don't trim, set - {maxCookieSize: 0, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //2 unlimited size: don't trim, set - {maxCookieSize: 800, expKeys: []string{"k1", "k5", "k4", "k3", "k6"}}, //3 trim to size and set - {maxCookieSize: 500, expKeys: []string{"k1", "k4", "k3"}}, //4 trim to size and set - {maxCookieSize: 200, expKeys: []string{}}, //5 insufficient size, trim to zero length and set - {maxCookieSize: -100, expKeys: []string{}}, //6 invalid size, trim to zero length and set - } - for i := range testCases { - processedCookie := writeThenRead(cookieToSend, testCases[i].maxCookieSize) - - actualKeys := make([]string, 0, 7) - for key := range processedCookie.uids { - actualKeys = append(actualKeys, key) - } - - assert.ElementsMatch(t, testCases[i].expKeys, actualKeys, "[Test %d]", i+1) - } -} - -func ensureEmptyMap(t *testing.T, cookie *Cookie) { - if !cookie.AllowSyncs() { - t.Error("Empty cookies should allow user syncs.") - } - if cookie.HasAnyLiveSyncs() { - t.Error("Empty cookies shouldn't have any user syncs. Found at least 1.") - } } func ensureConsistency(t *testing.T, cookie *Cookie) { + decoder := Base64Decoder{} + if cookie.AllowSyncs() { - err := cookie.TrySync("pulsepoint", "1") + err := cookie.Sync("pulsepoint", "1") if err != nil { t.Errorf("Cookie sync should succeed if the user has opted in.") } @@ -380,13 +807,14 @@ func ensureConsistency(t *testing.T, cookie *Cookie) { t.Error("If the user opted out, the PBSCookie should have no user syncs.") } - err := cookie.TrySync("adnxs", "123") + err := cookie.Sync("adnxs", "123") if err == nil { t.Error("TrySync should fail if the user has opted out of PBSCookie syncs, but it succeeded.") } } - - copiedCookie := ParseCookie(cookie.ToHTTPCookie(90 * 24 * time.Hour)) + httpCookie, err := ToHTTPCookie(cookie) + assert.NoError(t, err) + copiedCookie := decoder.Decode(httpCookie.Value) if copiedCookie.AllowSyncs() != cookie.AllowSyncs() { t.Error("The PBSCookie interface shouldn't let modifications happen if the user has opted out") } @@ -414,61 +842,17 @@ func ensureConsistency(t *testing.T, cookie *Cookie) { } } -func newTempId(uid string, offset int) uidWithExpiry { - return uidWithExpiry{ - UID: uid, - Expires: time.Now().Add(time.Duration(offset) * time.Minute).UTC(), +func ToHTTPCookie(cookie *Cookie) (*http.Cookie, error) { + encoder := Base64Encoder{} + encodedCookie, err := encoder.Encode(cookie) + if err != nil { + return nil, nil } -} -func newSampleCookie() *Cookie { - return &Cookie{ - uids: map[string]uidWithExpiry{ - "adnxs": newTempId("123", 10), - "rubicon": newTempId("456", 10), - }, - optOut: false, - } -} - -func writeThenRead(cookie *Cookie, maxCookieSize int) *Cookie { - w := httptest.NewRecorder() - hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: maxCookieSize} - cookie.SetCookieOnResponse(w, false, hostCookie, 90*24*time.Hour) - writtenCookie := w.HeaderMap.Get("Set-Cookie") - - header := http.Header{} - header.Add("Cookie", writtenCookie) - request := http.Request{Header: header} - return ParseCookieFromRequest(&request, hostCookie) -} - -func TestSetCookieOnResponseForSameSiteNone(t *testing.T) { - cookie := newSampleCookie() - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://www.prebid.com", nil) - ua := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" - req.Header.Set("User-Agent", ua) - hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: 0} - cookie.SetCookieOnResponse(w, true, hostCookie, 90*24*time.Hour) - writtenCookie := w.HeaderMap.Get("Set-Cookie") - t.Log("Set-Cookie is: ", writtenCookie) - if !strings.Contains(writtenCookie, "; Secure;") { - t.Error("Set-Cookie should contain Secure") - } -} - -func TestSetCookieOnResponseForOlderChromeVersion(t *testing.T) { - cookie := newSampleCookie() - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "http://www.prebid.com", nil) - ua := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3770.142 Safari/537.36" - req.Header.Set("User-Agent", ua) - hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: 0} - cookie.SetCookieOnResponse(w, false, hostCookie, 90*24*time.Hour) - writtenCookie := w.HeaderMap.Get("Set-Cookie") - t.Log("Set-Cookie is: ", writtenCookie) - if strings.Contains(writtenCookie, "SameSite=none") { - t.Error("Set-Cookie should not contain SameSite=none") - } + return &http.Cookie{ + Name: uidCookieName, + Value: encodedCookie, + Expires: time.Now().Add((90 * 24 * time.Hour)), + Path: "/", + }, nil } diff --git a/usersync/decoder.go b/usersync/decoder.go new file mode 100644 index 00000000000..a73e37fc560 --- /dev/null +++ b/usersync/decoder.go @@ -0,0 +1,27 @@ +package usersync + +import ( + "encoding/base64" + + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + +type Decoder interface { + Decode(encodedValue string) *Cookie +} + +type Base64Decoder struct{} + +func (d Base64Decoder) Decode(encodedValue string) *Cookie { + jsonValue, err := base64.URLEncoding.DecodeString(encodedValue) + if err != nil { + return NewCookie() + } + + var cookie Cookie + if err = jsonutil.UnmarshalValid(jsonValue, &cookie); err != nil { + return NewCookie() + } + + return &cookie +} diff --git a/usersync/ejector.go b/usersync/ejector.go new file mode 100644 index 00000000000..84299f72c12 --- /dev/null +++ b/usersync/ejector.go @@ -0,0 +1,127 @@ +package usersync + +import ( + "errors" + "time" +) + +type Ejector interface { + Choose(uids map[string]UIDEntry) (string, error) +} + +type OldestEjector struct{} + +type PriorityBidderEjector struct { + PriorityGroups [][]string + SyncersByBidder map[string]Syncer + IsSyncerPriority bool + TieEjector Ejector +} + +// Choose method for oldest ejector will return the oldest uid +func (o *OldestEjector) Choose(uids map[string]UIDEntry) (string, error) { + var oldestElement string + var oldestDate time.Time = time.Unix(1<<63-62135596801, 999999999) // Max value for time + + for key, value := range uids { + if value.Expires.Before(oldestDate) { + oldestElement = key + oldestDate = value.Expires + } + } + return oldestElement, nil +} + +// Choose method for priority ejector will return the oldest lowest priority element +func (p *PriorityBidderEjector) Choose(uids map[string]UIDEntry) (string, error) { + nonPriorityUids := getNonPriorityUids(uids, p.PriorityGroups, p.SyncersByBidder) + if err := p.checkSyncerPriority(nonPriorityUids); err != nil { + return "", err + } + + if len(nonPriorityUids) > 0 { + return p.TieEjector.Choose(nonPriorityUids) + } + + lowestPriorityGroup := p.PriorityGroups[len(p.PriorityGroups)-1] + if len(lowestPriorityGroup) == 1 { + uidToDelete := lowestPriorityGroup[0] + p.PriorityGroups = removeElementFromPriorityGroup(p.PriorityGroups, uidToDelete) + return uidToDelete, nil + } + + lowestPriorityUids := getPriorityUids(lowestPriorityGroup, uids, p.SyncersByBidder) + uidToDelete, err := p.TieEjector.Choose(lowestPriorityUids) + if err != nil { + return "", err + } + p.PriorityGroups = removeElementFromPriorityGroup(p.PriorityGroups, uidToDelete) + return uidToDelete, nil +} + +// updatePriorityGroup will remove the selected element from the priority groups, and will remove the entire priority group if it's empty +func removeElementFromPriorityGroup(priorityGroups [][]string, oldestElement string) [][]string { + lowestPriorityGroup := priorityGroups[len(priorityGroups)-1] + if len(lowestPriorityGroup) <= 1 { + return priorityGroups[:len(priorityGroups)-1] + } + + for index, elem := range lowestPriorityGroup { + if elem == oldestElement { + updatedPriorityGroup := append(lowestPriorityGroup[:index], lowestPriorityGroup[index+1:]...) + priorityGroups[len(priorityGroups)-1] = updatedPriorityGroup + return priorityGroups + } + } + return priorityGroups +} + +func getNonPriorityUids(uids map[string]UIDEntry, priorityGroups [][]string, syncersByBidder map[string]Syncer) map[string]UIDEntry { + // If no priority groups, then all keys in uids are non-priority + if len(priorityGroups) == 0 { + return uids + } + + // Create map of keys that are a priority + isPriority := make(map[string]bool) + for _, group := range priorityGroups { + for _, bidder := range group { + if bidderSyncer, ok := syncersByBidder[bidder]; ok { + isPriority[bidderSyncer.Key()] = true + } + } + } + + // Create a map for non-priority uids + nonPriorityUIDs := make(map[string]UIDEntry) + + // Loop over uids and populate the nonPriorityUIDs map with non-priority keys + for key, value := range uids { + if _, found := isPriority[key]; !found { + nonPriorityUIDs[key] = value + } + } + + return nonPriorityUIDs +} + +func getPriorityUids(lowestPriorityGroup []string, uids map[string]UIDEntry, syncersByBidder map[string]Syncer) map[string]UIDEntry { + lowestPriorityUIDs := make(map[string]UIDEntry) + + // Loop over lowestPriorityGroup and populate the lowestPriorityUIDs map + for _, bidder := range lowestPriorityGroup { + if bidderSyncer, ok := syncersByBidder[bidder]; ok { + if uidEntry, exists := uids[bidderSyncer.Key()]; exists { + lowestPriorityUIDs[bidderSyncer.Key()] = uidEntry + } + } + } + return lowestPriorityUIDs +} + +func (p *PriorityBidderEjector) checkSyncerPriority(nonPriorityUids map[string]UIDEntry) error { + if len(nonPriorityUids) == 1 && !p.IsSyncerPriority && len(p.PriorityGroups) > 0 { + return errors.New("syncer key is not a priority, and there are only priority elements left") + } + return nil +} diff --git a/usersync/ejector_test.go b/usersync/ejector_test.go new file mode 100644 index 00000000000..d13e7484b40 --- /dev/null +++ b/usersync/ejector_test.go @@ -0,0 +1,444 @@ +package usersync + +import ( + "errors" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPriorityEjector(t *testing.T) { + testCases := []struct { + name string + givenUids map[string]UIDEntry + givenEjector Ejector + expected string + expectedError error + }{ + { + name: "one-lowest-priority-element", + givenUids: map[string]UIDEntry{ + "highestPrioritySyncer": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + "lowestPriority": { + UID: "456", + Expires: time.Now(), + }, + }, + givenEjector: &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"highestPriorityBidder"}, + {"lowestPriority"}, + }, + SyncersByBidder: map[string]Syncer{ + "highestPriorityBidder": fakeSyncer{ + key: "highestPrioritySyncer", + }, + "lowestPriority": fakeSyncer{ + key: "lowestPriority", + }, + }, + IsSyncerPriority: true, + }, + expected: "lowestPriority", + }, + { + name: "multiple-uids-same-priority", + givenUids: map[string]UIDEntry{ + "newerButSamePriority": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + "olderButSamePriority": { + UID: "456", + Expires: time.Now(), + }, + }, + givenEjector: &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"newerButSamePriority", "olderButSamePriority"}, + }, + SyncersByBidder: map[string]Syncer{ + "newerButSamePriority": fakeSyncer{ + key: "newerButSamePriority", + }, + "olderButSamePriority": fakeSyncer{ + key: "olderButSamePriority", + }, + }, + IsSyncerPriority: true, + TieEjector: &OldestEjector{}, + }, + expected: "olderButSamePriority", + }, + { + name: "non-priority-uids-present", + givenUids: map[string]UIDEntry{ + "higherPriority": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + "lowestPriority": { + UID: "456", + Expires: time.Now(), + }, + "oldestNonPriority": { + UID: "456", + Expires: time.Now(), + }, + "newestNonPriority": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + }, + givenEjector: &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"higherPriority"}, + {"lowestPriority"}, + }, + SyncersByBidder: map[string]Syncer{ + "higherPriority": fakeSyncer{ + key: "higherPriority", + }, + "lowestPriority": fakeSyncer{ + key: "lowestPriority", + }, + "oldestNonPriority": fakeSyncer{ + key: "oldestNonPriority", + }, + "newestNonPriority": fakeSyncer{ + key: "newestNonPriority", + }, + }, + IsSyncerPriority: true, + TieEjector: &OldestEjector{}, + }, + expected: "oldestNonPriority", + }, + { + name: "empty-priority-groups", + givenUids: map[string]UIDEntry{ + "oldestNonPriority": { + UID: "456", + Expires: time.Now(), + }, + "newestNonPriority": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + }, + givenEjector: &PriorityBidderEjector{ + SyncersByBidder: map[string]Syncer{ + "oldestNonPriority": fakeSyncer{ + key: "oldestNonPriority", + }, + "newestNonPriority": fakeSyncer{ + key: "newestNonPriority", + }, + }, + IsSyncerPriority: false, + TieEjector: &OldestEjector{}, + }, + expected: "oldestNonPriority", + }, + { + name: "one-priority-element", + givenUids: map[string]UIDEntry{ + "onlyPriorityElement": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + }, + givenEjector: &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"onlyPriorityElement"}, + }, + SyncersByBidder: map[string]Syncer{ + "onlyPriorityElement": fakeSyncer{ + key: "onlyPriorityElement", + }, + }, + IsSyncerPriority: true, + }, + expected: "onlyPriorityElement", + }, + { + name: "syncer-is-not-priority", + givenUids: map[string]UIDEntry{ + "onlyPriorityElement": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + "syncer": { + UID: "456", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + }, + givenEjector: &PriorityBidderEjector{ + PriorityGroups: [][]string{ + {"onlyPriorityElement"}, + }, + SyncersByBidder: map[string]Syncer{ + "onlyPriorityElement": fakeSyncer{ + key: "onlyPriorityElement", + }, + }, + IsSyncerPriority: false, + }, + expectedError: errors.New("syncer key is not a priority, and there are only priority elements left"), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + uidToDelete, err := test.givenEjector.Choose(test.givenUids) + if test.expectedError != nil { + assert.Equal(t, test.expectedError, err) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expected, uidToDelete) + } + }) + } +} + +func TestOldestEjector(t *testing.T) { + testCases := []struct { + name string + givenUids map[string]UIDEntry + expected string + }{ + { + name: "multiple-elements", + givenUids: map[string]UIDEntry{ + "newestElement": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + "oldestElement": { + UID: "456", + Expires: time.Now(), + }, + }, + expected: "oldestElement", + }, + { + name: "one-element", + givenUids: map[string]UIDEntry{ + "onlyElement": { + UID: "123", + Expires: time.Now().Add((90 * 24 * time.Hour)), + }, + }, + expected: "onlyElement", + }, + { + name: "no-elements", + givenUids: map[string]UIDEntry{}, + expected: "", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + ejector := OldestEjector{} + oldestElement, err := ejector.Choose(test.givenUids) + assert.NoError(t, err) + assert.Equal(t, test.expected, oldestElement) + }) + } +} + +func TestGetNonPriorityUids(t *testing.T) { + syncersByBidder := map[string]Syncer{ + "syncerKey1": fakeSyncer{ + key: "syncerKey1", + }, + "syncerKey2": fakeSyncer{ + key: "syncerKey2", + }, + "syncerKey3": fakeSyncer{ + key: "syncerKey3", + }, + } + + testCases := []struct { + name string + givenUids map[string]UIDEntry + givenPriorityGroups [][]string + expected map[string]UIDEntry + }{ + { + name: "one-priority-group", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + givenPriorityGroups: [][]string{ + {"syncerKey1"}, + }, + expected: map[string]UIDEntry{ + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + }, + { + name: "multiple-priority-groups", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + givenPriorityGroups: [][]string{ + {"syncerKey1"}, + {"syncerKey2"}, + }, + expected: map[string]UIDEntry{ + "syncerKey3": { + UID: "789", + }, + }, + }, + { + name: "no-priority-groups", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + expected: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + uids := getNonPriorityUids(test.givenUids, test.givenPriorityGroups, syncersByBidder) + assert.Equal(t, true, reflect.DeepEqual(test.expected, uids)) + }) + } +} + +func TestGetPriorityUids(t *testing.T) { + syncersByBidder := map[string]Syncer{ + "syncerKey1": fakeSyncer{ + key: "syncerKey1", + }, + "syncerKey2": fakeSyncer{ + key: "syncerKey2", + }, + "syncerKey3": fakeSyncer{ + key: "syncerKey3", + }, + } + + testCases := []struct { + name string + givenUids map[string]UIDEntry + givenLowestPriorityGroup []string + expected map[string]UIDEntry + }{ + { + name: "one-priority-element", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + givenLowestPriorityGroup: []string{"syncerKey1"}, + expected: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + }, + }, + { + name: "multiple-priority-elements", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + givenLowestPriorityGroup: []string{"syncerKey1", "syncerKey2"}, + expected: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + }, + }, + { + name: "no-priority-elements", + givenUids: map[string]UIDEntry{ + "syncerKey1": { + UID: "123", + }, + "syncerKey2": { + UID: "456", + }, + "syncerKey3": { + UID: "789", + }, + }, + givenLowestPriorityGroup: []string{}, + expected: map[string]UIDEntry{}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + uids := getPriorityUids(test.givenLowestPriorityGroup, test.givenUids, syncersByBidder) + assert.Equal(t, true, reflect.DeepEqual(test.expected, uids)) + }) + } +} diff --git a/usersync/encoder.go b/usersync/encoder.go new file mode 100644 index 00000000000..2baf5d86524 --- /dev/null +++ b/usersync/encoder.go @@ -0,0 +1,24 @@ +package usersync + +import ( + "encoding/base64" + + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + +type Encoder interface { + // Encode a cookie into a base 64 string + Encode(c *Cookie) (string, error) +} + +type Base64Encoder struct{} + +func (e Base64Encoder) Encode(c *Cookie) (string, error) { + j, err := jsonutil.Marshal(c) + if err != nil { + return "", err + } + b64 := base64.URLEncoding.EncodeToString(j) + + return b64, nil +} diff --git a/usersync/encoder_decoder_test.go b/usersync/encoder_decoder_test.go new file mode 100644 index 00000000000..5a87d4e7c82 --- /dev/null +++ b/usersync/encoder_decoder_test.go @@ -0,0 +1,149 @@ +package usersync + +import ( + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestEncoderDecoder(t *testing.T) { + encoder := Base64Encoder{} + decoder := Base64Decoder{} + + testCases := []struct { + name string + givenRequest *http.Request + givenHttpCookie *http.Cookie + givenCookie *Cookie + givenDecoder Decoder + expectedCookie *Cookie + }{ + { + name: "simple-cookie", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + }, + }, + optOut: false, + }, + }, + { + name: "empty-cookie", + givenCookie: &Cookie{}, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: false, + }, + }, + { + name: "nil-cookie", + givenCookie: nil, + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{}, + optOut: false, + }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + encodedCookie, err := encoder.Encode(test.givenCookie) + assert.NoError(t, err) + decodedCookie := decoder.Decode(encodedCookie) + + assert.Equal(t, test.expectedCookie.uids, decodedCookie.uids) + assert.Equal(t, test.expectedCookie.optOut, decodedCookie.optOut) + }) + } +} + +func TestEncoder(t *testing.T) { + encoder := Base64Encoder{} + + testCases := []struct { + name string + givenCookie *Cookie + expectedEncodedCookie string + }{ + { + name: "simple-cookie", + givenCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + Expires: time.Time{}, + }, + }, + optOut: false, + }, + expectedEncodedCookie: "eyJ0ZW1wVUlEcyI6eyJhZG54cyI6eyJ1aWQiOiJVSUQiLCJleHBpcmVzIjoiMDAwMS0wMS0wMVQwMDowMDowMFoifX19", + }, + { + name: "empty-cookie", + givenCookie: &Cookie{}, + expectedEncodedCookie: "e30=", + }, + { + name: "nil-cookie", + givenCookie: nil, + expectedEncodedCookie: "bnVsbA==", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + encodedCookie, err := encoder.Encode(test.givenCookie) + assert.NoError(t, err) + + assert.Equal(t, test.expectedEncodedCookie, encodedCookie) + }) + } +} + +func TestDecoder(t *testing.T) { + decoder := Base64Decoder{} + + testCases := []struct { + name string + givenEncodedCookie string + expectedCookie *Cookie + }{ + { + name: "simple-encoded-cookie", + givenEncodedCookie: "eyJ0ZW1wVUlEcyI6eyJhZG54cyI6eyJ1aWQiOiJVSUQiLCJleHBpcmVzIjoiMDAwMS0wMS0wMVQwMDowMDowMFoifX19", + expectedCookie: &Cookie{ + uids: map[string]UIDEntry{ + "adnxs": { + UID: "UID", + }, + }, + optOut: false, + }, + }, + { + name: "nil-encoded-cookie", + givenEncodedCookie: "", + expectedCookie: NewCookie(), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + decodedCookie := decoder.Decode(test.givenEncodedCookie) + assert.Equal(t, test.expectedCookie, decodedCookie) + }) + } +} diff --git a/usersync/syncer.go b/usersync/syncer.go index 2e0e41027b5..50985eca5be 100644 --- a/usersync/syncer.go +++ b/usersync/syncer.go @@ -9,16 +9,15 @@ import ( "text/template" validator "github.com/asaskevich/govalidator" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" ) var ( - errNoSyncTypesProvided = errors.New("no sync types provided") - errNoSyncTypesSupported = errors.New("no sync types supported") - errDefaultTypeMissingIFrame = errors.New("default is set to iframe but no iframe endpoint is configured") - errDefaultTypeMissingRedirect = errors.New("default is set to redirect but no redirect endpoint is configured") + ErrSyncerEndpointRequired = errors.New("at least one endpoint (iframe and/or redirect) is required") + ErrSyncerKeyRequired = errors.New("key is required") + errNoSyncTypesProvided = errors.New("no sync types provided") + errNoSyncTypesSupported = errors.New("no sync types supported") ) // Syncer represents the user sync configuration for a bidder or a shared set of bidders. @@ -27,15 +26,15 @@ type Syncer interface { // necessarily, a one-to-one mapping with a bidder. Key() string - // DefaultSyncType is the default SyncType for this syncer. - DefaultSyncType() SyncType + // DefaultResponseFormat is the default SyncType for this syncer. + DefaultResponseFormat() SyncType // SupportsType returns true if the syncer supports at least one of the specified sync types. SupportsType(syncTypes []SyncType) bool // GetSync returns a user sync for the user's device to perform, or an error if the none of the // sync types are supported or if macro substitution fails. - GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) + GetSync(syncTypes []SyncType, userSyncMacros macros.UserSyncPrivacy) (Sync, error) } // Sync represents a user sync to be performed by the user's device. @@ -51,19 +50,12 @@ type standardSyncer struct { iframe *template.Template redirect *template.Template supportCORS bool + formatOverride string } -const ( - setuidSyncTypeIFrame = "b" // b = blank HTML response - setuidSyncTypeRedirect = "i" // i = image response -) - -var ErrSyncerEndpointRequired = errors.New("at least one endpoint (iframe and/or redirect) is required") -var ErrSyncerKeyRequired = errors.New("key is required") - // NewSyncer creates a new Syncer from the provided configuration, or return an error if macro substition // fails or an endpoint url is invalid. -func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, error) { +func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer, bidder string) (Syncer, error) { if syncerConfig.Key == "" { return nil, ErrSyncerKeyRequired } @@ -76,11 +68,12 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, key: syncerConfig.Key, defaultSyncType: resolveDefaultSyncType(syncerConfig), supportCORS: syncerConfig.SupportCORS != nil && *syncerConfig.SupportCORS, + formatOverride: syncerConfig.FormatOverride, } if syncerConfig.IFrame != nil { var err error - syncer.iframe, err = buildTemplate(syncerConfig.Key, setuidSyncTypeIFrame, hostConfig, syncerConfig.ExternalURL, *syncerConfig.IFrame) + syncer.iframe, err = buildTemplate(bidder, config.SyncResponseFormatIFrame, hostConfig, syncerConfig.ExternalURL, *syncerConfig.IFrame, syncerConfig.FormatOverride) if err != nil { return nil, fmt.Errorf("iframe %v", err) } @@ -91,7 +84,7 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, if syncerConfig.Redirect != nil { var err error - syncer.redirect, err = buildTemplate(syncerConfig.Key, setuidSyncTypeRedirect, hostConfig, syncerConfig.ExternalURL, *syncerConfig.Redirect) + syncer.redirect, err = buildTemplate(bidder, config.SyncResponseFormatRedirect, hostConfig, syncerConfig.ExternalURL, *syncerConfig.Redirect, syncerConfig.FormatOverride) if err != nil { return nil, fmt.Errorf("redirect %v", err) } @@ -114,21 +107,27 @@ func resolveDefaultSyncType(syncerConfig config.Syncer) SyncType { var ( macroRegexExternalHost = regexp.MustCompile(`{{\s*\.ExternalURL\s*}}`) macroRegexSyncerKey = regexp.MustCompile(`{{\s*\.SyncerKey\s*}}`) + macroRegexBidderName = regexp.MustCompile(`{{\s*\.BidderName\s*}}`) macroRegexSyncType = regexp.MustCompile(`{{\s*\.SyncType\s*}}`) macroRegexUserMacro = regexp.MustCompile(`{{\s*\.UserMacro\s*}}`) macroRegexRedirect = regexp.MustCompile(`{{\s*\.RedirectURL\s*}}`) macroRegex = regexp.MustCompile(`{{\s*\..*?\s*}}`) ) -func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncerExternalURL string, syncerEndpoint config.SyncerEndpoint) (*template.Template, error) { +func buildTemplate(bidderName, syncTypeValue string, hostConfig config.UserSync, syncerExternalURL string, syncerEndpoint config.SyncerEndpoint, formatOverride string) (*template.Template, error) { redirectTemplate := syncerEndpoint.RedirectURL if redirectTemplate == "" { redirectTemplate = hostConfig.RedirectURL } + if formatOverride != "" { + syncTypeValue = formatOverride + } + externalURL := chooseExternalURL(syncerEndpoint.ExternalURL, syncerExternalURL, hostConfig.ExternalURL) - redirectURL := macroRegexSyncerKey.ReplaceAllLiteralString(redirectTemplate, key) + redirectURL := macroRegexSyncerKey.ReplaceAllLiteralString(redirectTemplate, bidderName) + redirectURL = macroRegexBidderName.ReplaceAllLiteralString(redirectURL, bidderName) redirectURL = macroRegexSyncType.ReplaceAllLiteralString(redirectURL, syncTypeValue) redirectURL = macroRegexUserMacro.ReplaceAllLiteralString(redirectURL, syncerEndpoint.UserMacro) redirectURL = macroRegexExternalHost.ReplaceAllLiteralString(redirectURL, externalURL) @@ -136,7 +135,7 @@ func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncer url := macroRegexRedirect.ReplaceAllString(syncerEndpoint.URL, redirectURL) - templateName := strings.ToLower(key) + "_usersync_url" + templateName := strings.ToLower(bidderName) + "_usersync_url" return template.New(templateName).Parse(url) } @@ -168,7 +167,7 @@ func escapeTemplate(x string) string { return escaped.String() } -var templateTestValues = macros.UserSyncTemplateParams{ +var templateTestValues = macros.UserSyncPrivacy{ GDPR: "anyGDPR", GDPRConsent: "anyGDPRConsent", USPrivacy: "anyCCPAConsent", @@ -191,8 +190,15 @@ func (s standardSyncer) Key() string { return s.key } -func (s standardSyncer) DefaultSyncType() SyncType { - return s.defaultSyncType +func (s standardSyncer) DefaultResponseFormat() SyncType { + switch s.formatOverride { + case config.SyncResponseFormatIFrame: + return SyncTypeIFrame + case config.SyncResponseFormatRedirect: + return SyncTypeRedirect + default: + return s.defaultSyncType + } } func (s standardSyncer) SupportsType(syncTypes []SyncType) bool { @@ -217,7 +223,7 @@ func (s standardSyncer) filterSupportedSyncTypes(syncTypes []SyncType) []SyncTyp return supported } -func (s standardSyncer) GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) { +func (s standardSyncer) GetSync(syncTypes []SyncType, userSyncMacros macros.UserSyncPrivacy) (Sync, error) { syncType, err := s.chooseSyncType(syncTypes) if err != nil { return Sync{}, err @@ -225,13 +231,7 @@ func (s standardSyncer) GetSync(syncTypes []SyncType, privacyPolicies privacy.Po syncTemplate := s.chooseTemplate(syncType) - url, err := macros.ResolveMacros(syncTemplate, macros.UserSyncTemplateParams{ - GDPR: privacyPolicies.GDPR.Signal, - GDPRConsent: privacyPolicies.GDPR.Consent, - USPrivacy: privacyPolicies.CCPA.Consent, - GPP: privacyPolicies.GPP.Consent, - GPPSID: privacyPolicies.GPP.RawSID, - }) + url, err := macros.ResolveMacros(syncTemplate, userSyncMacros) if err != nil { return Sync{}, err } diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go index 6e727c3a49c..c9c568e299a 100644 --- a/usersync/syncer_test.go +++ b/usersync/syncer_test.go @@ -4,11 +4,8 @@ import ( "testing" "text/template" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" "github.com/stretchr/testify/assert" ) @@ -16,7 +13,7 @@ func TestNewSyncer(t *testing.T) { var ( supportCORS = true hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} - macroValues = macros.UserSyncTemplateParams{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} + macroValues = macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} redirectConfig = &config.SyncerEndpoint{URL: "https://bidder.com/redirect?redirect={{.RedirectURL}}"} errParseConfig = &config.SyncerEndpoint{URL: "{{malformed}}"} @@ -26,9 +23,11 @@ func TestNewSyncer(t *testing.T) { testCases := []struct { description string givenKey string + givenBidderName string givenIFrameConfig *config.SyncerEndpoint givenRedirectConfig *config.SyncerEndpoint givenExternalURL string + givenForceType string expectedError string expectedDefault SyncType expectedIFrame string @@ -37,6 +36,7 @@ func TestNewSyncer(t *testing.T) { { description: "Missing Key", givenKey: "", + givenBidderName: "", givenIFrameConfig: iframeConfig, givenRedirectConfig: nil, expectedError: "key is required", @@ -44,6 +44,7 @@ func TestNewSyncer(t *testing.T) { { description: "Missing Endpoints", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: nil, givenRedirectConfig: nil, expectedError: "at least one endpoint (iframe and/or redirect) is required", @@ -51,6 +52,7 @@ func TestNewSyncer(t *testing.T) { { description: "IFrame & Redirect Endpoints", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: iframeConfig, givenRedirectConfig: redirectConfig, expectedDefault: SyncTypeIFrame, @@ -60,13 +62,15 @@ func TestNewSyncer(t *testing.T) { { description: "IFrame - Parse Error", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: errParseConfig, givenRedirectConfig: nil, - expectedError: "iframe template: a_usersync_url:1: function \"malformed\" not defined", + expectedError: "iframe template: biddera_usersync_url:1: function \"malformed\" not defined", }, { description: "IFrame - Validation Error", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: errInvalidConfig, givenRedirectConfig: nil, expectedError: "iframe composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", @@ -74,13 +78,15 @@ func TestNewSyncer(t *testing.T) { { description: "Redirect - Parse Error", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: nil, givenRedirectConfig: errParseConfig, - expectedError: "redirect template: a_usersync_url:1: function \"malformed\" not defined", + expectedError: "redirect template: biddera_usersync_url:1: function \"malformed\" not defined", }, { description: "Redirect - Validation Error", givenKey: "a", + givenBidderName: "bidderA", givenIFrameConfig: nil, givenRedirectConfig: errInvalidConfig, expectedError: "redirect composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", @@ -88,6 +94,7 @@ func TestNewSyncer(t *testing.T) { { description: "Syncer Level External URL", givenKey: "a", + givenBidderName: "bidderA", givenExternalURL: "http://syncer.com", givenIFrameConfig: iframeConfig, givenRedirectConfig: redirectConfig, @@ -106,7 +113,7 @@ func TestNewSyncer(t *testing.T) { ExternalURL: test.givenExternalURL, } - result, err := NewSyncer(hostConfig, syncerConfig) + result, err := NewSyncer(hostConfig, syncerConfig, test.givenBidderName) if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -177,7 +184,7 @@ func TestBuildTemplate(t *testing.T) { key = "anyKey" syncTypeValue = "x" hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} - macroValues = macros.UserSyncTemplateParams{GDPR: "A", GDPRConsent: "B", USPrivacy: "C", GPP: "D", GPPSID: "1"} + macroValues = macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C", GPP: "D", GPPSID: "1"} ) testCases := []struct { @@ -196,7 +203,7 @@ func TestBuildTemplate(t *testing.T) { expectedRendered: "hasNoComposedMacros,gdpr=A", }, { - description: "All Composed Macros", + description: "All Composed Macros - SyncerKey", givenSyncerEndpoint: config.SyncerEndpoint{ URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&f={{.SyncType}}&gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&uid={{.UserMacro}}", @@ -205,6 +212,16 @@ func TestBuildTemplate(t *testing.T) { }, expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26gpp%3DD%26gpp_sid%3D1%26uid%3D%24UID%24", }, + { + description: "All Composed Macros - BidderName", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.BidderName}}&f={{.SyncType}}&gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&uid={{.UserMacro}}", + ExternalURL: "http://syncer.com", + UserMacro: "$UID$", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26gpp%3DD%26gpp_sid%3D1%26uid%3D%24UID%24", + }, { description: "Redirect URL + External URL From Host", givenSyncerEndpoint: config.SyncerEndpoint{ @@ -306,7 +323,7 @@ func TestBuildTemplate(t *testing.T) { } for _, test := range testCases { - result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint) + result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint, "") if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -432,7 +449,7 @@ func TestValidateTemplate(t *testing.T) { { description: "Contains Unrecognized Macro", given: template.Must(template.New("test").Parse("invalid:{{.DoesNotExist}}")), - expectedError: "template: test:1:10: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncTemplateParams", + expectedError: "template: test:1:10: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncPrivacy", }, { description: "Not A Url", @@ -464,7 +481,36 @@ func TestSyncerKey(t *testing.T) { func TestSyncerDefaultSyncType(t *testing.T) { syncer := standardSyncer{defaultSyncType: SyncTypeRedirect} - assert.Equal(t, SyncTypeRedirect, syncer.DefaultSyncType()) + assert.Equal(t, SyncTypeRedirect, syncer.DefaultResponseFormat()) +} + +func TestSyncerDefaultResponseFormat(t *testing.T) { + testCases := []struct { + description string + givenSyncer standardSyncer + expectedSyncType SyncType + }{ + { + description: "IFrame", + givenSyncer: standardSyncer{formatOverride: config.SyncResponseFormatIFrame}, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "Default with Redirect Override", + givenSyncer: standardSyncer{defaultSyncType: SyncTypeIFrame, formatOverride: config.SyncResponseFormatRedirect}, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "Default with no override", + givenSyncer: standardSyncer{defaultSyncType: SyncTypeRedirect}, + expectedSyncType: SyncTypeRedirect, + }, + } + + for _, test := range testCases { + syncType := test.givenSyncer.DefaultResponseFormat() + assert.Equal(t, test.expectedSyncType, syncType, test.description) + } } func TestSyncerSupportsType(t *testing.T) { @@ -617,45 +663,45 @@ func TestSyncerGetSync(t *testing.T) { ) testCases := []struct { - description string - givenSyncer standardSyncer - givenSyncTypes []SyncType - givenPrivacyPolicies privacy.Policies - expectedError string - expectedSync Sync + description string + givenSyncer standardSyncer + givenSyncTypes []SyncType + givenMacros macros.UserSyncPrivacy + expectedError string + expectedSync Sync }{ { - description: "No Sync Types", - givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, - givenSyncTypes: []SyncType{}, - givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, - expectedError: "no sync types provided", + description: "No Sync Types", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{}, + givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, + expectedError: "no sync types provided", }, { - description: "IFrame", - givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, - givenSyncTypes: []SyncType{SyncTypeIFrame}, - givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, - expectedSync: Sync{URL: "iframe,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeIFrame, SupportCORS: false}, + description: "IFrame", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, + expectedSync: Sync{URL: "iframe,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeIFrame, SupportCORS: false}, }, { - description: "Redirect", - givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, - givenSyncTypes: []SyncType{SyncTypeRedirect}, - givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, - expectedSync: Sync{URL: "redirect,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeRedirect, SupportCORS: false}, + description: "Redirect", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{SyncTypeRedirect}, + givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, + expectedSync: Sync{URL: "redirect,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeRedirect, SupportCORS: false}, }, { - description: "Macro Error", - givenSyncer: standardSyncer{iframe: malformedTemplate}, - givenSyncTypes: []SyncType{SyncTypeIFrame}, - givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, - expectedError: "template: test:1:20: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncTemplateParams", + description: "Macro Error", + givenSyncer: standardSyncer{iframe: malformedTemplate}, + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, + expectedError: "template: test:1:20: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncPrivacy", }, } for _, test := range testCases { - result, err := test.givenSyncer.GetSync(test.givenSyncTypes, test.givenPrivacyPolicies) + result, err := test.givenSyncer.GetSync(test.givenSyncTypes, test.givenMacros) if test.expectedError == "" { assert.NoError(t, err, test.description+":err") diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go index c521d36dde2..9c916b821b4 100644 --- a/usersync/syncersbuilder.go +++ b/usersync/syncersbuilder.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v2/config" ) type namedSyncerConfig struct { @@ -59,17 +59,16 @@ func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInf continue } - syncer, err := NewSyncer(hostUserSyncConfig, primaryCfg.cfg) - if err != nil { - errs = append(errs, SyncerBuildError{ - Bidder: primaryCfg.name, - SyncerKey: key, - Err: err, - }) - continue - } - for _, bidder := range cfgGroup { + syncer, err := NewSyncer(hostUserSyncConfig, primaryCfg.cfg, bidder.name) + if err != nil { + errs = append(errs, SyncerBuildError{ + Bidder: primaryCfg.name, + SyncerKey: key, + Err: err, + }) + continue + } syncers[bidder.name] = syncer } } diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go index b3672a501bb..a8f396aa714 100644 --- a/usersync/syncersbuilder_test.go +++ b/usersync/syncersbuilder_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" "github.com/stretchr/testify/assert" ) @@ -48,7 +48,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated}, expectedIFramesURLs: map[string]string{ - "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder1%2Fhost", }, }, { @@ -64,7 +64,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError}, expectedErrors: []string{ - "cannot create syncer for bidder bidder1 with key a: iframe template: a_usersync_url:1: function \"xRedirectURL\" not defined", + "cannot create syncer for bidder bidder1 with key a: iframe template: bidder1_usersync_url:1: function \"xRedirectURL\" not defined", }, }, { @@ -72,8 +72,8 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyBPopulated}, expectedIFramesURLs: map[string]string{ - "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", - "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder1%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder2%2Fhost", }, }, { @@ -81,8 +81,8 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyAEmpty}, expectedIFramesURLs: map[string]string{ - "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", - "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder1%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder2%2Fhost", }, }, { @@ -106,7 +106,8 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAError}, expectedErrors: []string{ - "cannot create syncer for bidder bidder2 with key a: iframe template: a_usersync_url:1: function \"xRedirectURL\" not defined", + "cannot create syncer for bidder bidder2 with key a: iframe template: bidder1_usersync_url:1: function \"xRedirectURL\" not defined", + "cannot create syncer for bidder bidder2 with key a: iframe template: bidder2_usersync_url:1: function \"xRedirectURL\" not defined", }, }, { @@ -114,7 +115,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": {}, "bidder2": infoKeyBPopulated}, expectedIFramesURLs: map[string]string{ - "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder2%2Fhost", }, }, { @@ -122,7 +123,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyADisabled, "bidder2": infoKeyBPopulated}, expectedIFramesURLs: map[string]string{ - "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder2%2Fhost", }, }, { @@ -130,7 +131,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyASupportsOnly, "bidder2": infoKeyBPopulated}, expectedIFramesURLs: map[string]string{ - "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder2%2Fhost", }, }, { @@ -138,7 +139,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: hostConfig, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError, "bidder2": infoKeyBEmpty}, expectedErrors: []string{ - "cannot create syncer for bidder bidder1 with key a: iframe template: a_usersync_url:1: function \"xRedirectURL\" not defined", + "cannot create syncer for bidder bidder1 with key a: iframe template: bidder1_usersync_url:1: function \"xRedirectURL\" not defined", "cannot create syncer for bidder bidder2 with key b: at least one endpoint (iframe and/or redirect) is required", }, }, @@ -147,7 +148,7 @@ func TestBuildSyncers(t *testing.T) { givenConfig: config.Configuration{ExternalURL: "http://host.com", UserSync: config.UserSync{ExternalURL: "http://hostoverride.com", RedirectURL: "{{.ExternalURL}}/{{.SyncerKey}}/host"}}, givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated}, expectedIFramesURLs: map[string]string{ - "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhostoverride.com%2Fa%2Fhost", + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhostoverride.com%2Fbidder1%2Fhost", }, }, } @@ -159,7 +160,7 @@ func TestBuildSyncers(t *testing.T) { assert.Empty(t, errs, test.description+":err") resultRenderedIFrameURLS := map[string]string{} for k, v := range result { - iframeRendered, err := v.GetSync([]SyncType{SyncTypeIFrame}, privacy.Policies{}) + iframeRendered, err := v.GetSync([]SyncType{SyncTypeIFrame}, macros.UserSyncPrivacy{}) if assert.NoError(t, err, test.description+"key:%s,:iframe_render", k) { resultRenderedIFrameURLS[k] = iframeRendered.URL } diff --git a/util/httputil/httputil.go b/util/httputil/httputil.go index 28334a54b87..cabb197fa37 100644 --- a/util/httputil/httputil.go +++ b/util/httputil/httputil.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/util/iputil" ) var ( diff --git a/util/httputil/httputil_test.go b/util/httputil/httputil_test.go index 5da3b0ab735..d056db245f3 100644 --- a/util/httputil/httputil_test.go +++ b/util/httputil/httputil_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v2/util/iputil" "github.com/stretchr/testify/assert" ) diff --git a/util/iputil/parse.go b/util/iputil/parse.go index bcb00760e22..c226ece8e5e 100644 --- a/util/iputil/parse.go +++ b/util/iputil/parse.go @@ -15,6 +15,14 @@ const ( IPv6 IPVersion = 6 ) +const ( + IPv4BitSize = 32 + IPv6BitSize = 128 + + IPv4DefaultMaskingBitSize = 24 + IPv6DefaultMaskingBitSize = 56 +) + // ParseIP parses v as an ip address returning the result and version, or nil and unknown if invalid. func ParseIP(v string) (net.IP, IPVersion) { if ip := net.ParseIP(v); ip != nil { diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 3b468731cad..b5bb47cca9a 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -4,14 +4,16 @@ import ( "bytes" "encoding/json" "io" + "strings" + + jsoniter "github.com/json-iterator/go" + "github.com/prebid/prebid-server/v2/errortypes" ) -var comma = []byte(",")[0] -var colon = []byte(":")[0] -var sqBracket = []byte("]")[0] -var openCurlyBracket = []byte("{")[0] -var closingCurlyBracket = []byte("}")[0] -var quote = []byte(`"`)[0] +var comma = byte(',') +var colon = byte(':') +var sqBracket = byte(']') +var closingCurlyBracket = byte('}') // Finds element in json byte array with any level of nesting func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) { @@ -110,3 +112,102 @@ func DropElement(extension []byte, elementNames ...string) ([]byte, error) { } return extension, nil } + +// jsonConfigValidationOn attempts to maintain compatibility with the standard library which +// includes enabling validation +var jsonConfigValidationOn = jsoniter.ConfigCompatibleWithStandardLibrary + +// jsonConfigValidationOff disables validation +var jsonConfigValidationOff = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: false, +}.Froze() + +// Unmarshal unmarshals a byte slice into the specified data structure without performing +// any validation on the data. An unmarshal error is returned if a non-validation error occurs. +func Unmarshal(data []byte, v interface{}) error { + err := jsonConfigValidationOff.Unmarshal(data, v) + if err != nil { + return &errortypes.FailedToUnmarshal{ + Message: tryExtractErrorMessage(err), + } + } + return nil +} + +// UnmarshalValid validates and unmarshals a byte slice into the specified data structure +// returning an error if validation fails +func UnmarshalValid(data []byte, v interface{}) error { + if err := jsonConfigValidationOn.Unmarshal(data, v); err != nil { + return &errortypes.FailedToUnmarshal{ + Message: tryExtractErrorMessage(err), + } + } + return nil +} + +// Marshal marshals a data structure into a byte slice without performing any validation +// on the data. A marshal error is returned if a non-validation error occurs. +func Marshal(v interface{}) ([]byte, error) { + data, err := jsonConfigValidationOn.Marshal(v) + if err != nil { + return nil, &errortypes.FailedToMarshal{ + Message: err.Error(), + } + } + return data, nil +} + +// tryExtractErrorMessage attempts to extract a sane error message from the json-iter package. The errors +// returned from that library are not types and include a lot of extra information we don't want to respond with. +// This is hacky, but it's the only downside to the json-iter library. +func tryExtractErrorMessage(err error) string { + msg := err.Error() + + msgEndIndex := strings.LastIndex(msg, ", error found in #") + if msgEndIndex == -1 { + return msg + } + + msgStartIndex := strings.Index(msg, ": ") + if msgStartIndex == -1 { + return msg + } + + operationStack := []string{msg[0:msgStartIndex]} + for { + msgStartIndexNext := strings.Index(msg[msgStartIndex+2:], ": ") + + // no more matches + if msgStartIndexNext == -1 { + break + } + + // matches occur after the end message marker (sanity check) + if (msgStartIndex + msgStartIndexNext) >= msgEndIndex { + break + } + + // match should not contain a space, indicates operation is really an error message + match := msg[msgStartIndex+2 : msgStartIndex+2+msgStartIndexNext] + if strings.Contains(match, " ") { + break + } + + operationStack = append(operationStack, match) + msgStartIndex += msgStartIndexNext + 2 + } + + if len(operationStack) > 1 && isLikelyDetailedErrorMessage(msg[msgStartIndex+2:]) { + return "cannot unmarshal " + operationStack[len(operationStack)-2] + ": " + msg[msgStartIndex+2:msgEndIndex] + } + + return msg[msgStartIndex+2 : msgEndIndex] +} + +// isLikelyDetailedErrorMessage checks if the json unmarshal error contains enough information such +// that the caller clearly understands the context, where the structure name is not needed. +func isLikelyDetailedErrorMessage(msg string) bool { + return !strings.HasPrefix(msg, "request.") +} diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index 12b1fd5e803..09fb6727309 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -1,13 +1,14 @@ package jsonutil import ( - "github.com/stretchr/testify/assert" + "errors" "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestDropElement(t *testing.T) { - tests := []struct { description string input []byte @@ -183,3 +184,59 @@ func TestDropElement(t *testing.T) { } } } + +func TestTryExtractErrorMessage(t *testing.T) { + tests := []struct { + name string + givenErr string + expectedMsg string + }{ + { + name: "level-1", + givenErr: "readObjectStart: expect { or n, but found m, error found in #1 byte of ...|malformed|..., bigger context ...|malformed|..", + expectedMsg: "expect { or n, but found m", + }, + { + name: "level-2", + givenErr: "openrtb_ext.ExtRequestPrebidCache.Bids: readObjectStart: expect { or n, but found t, error found in #10 byte of ...|:{\"bids\":true}}|..., bigger context ...|{\"cache\":{\"bids\":true}}|...", + expectedMsg: "cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t", + }, + { + name: "level-3+", + givenErr: "openrtb_ext.ExtRequestPrebid.Cache: openrtb_ext.ExtRequestPrebidCache.Bids: readObjectStart: expect { or n, but found t, error found in #10 byte of ...|:{\"bids\":true}}|..., bigger context ...|{\"cache\":{\"bids\":true}}|...", + expectedMsg: "cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t", + }, + { + name: "error-msg", + givenErr: "Skip: do not know how to skip: 109, error found in #10 byte of ...|prebid\": malformed}|..., bigger context ...|{\"prebid\": malformed}|...", + expectedMsg: "do not know how to skip: 109", + }, + { + name: "specific", + givenErr: "openrtb_ext.ExtDevicePrebid.Interstitial: unmarshalerDecoder: request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100, error found in #10 byte of ...| }\n }|..., bigger context ...|: 120,\n \"minheightperc\": 60\n }\n }|...", + expectedMsg: "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100", + }, + { + name: "normal", + givenErr: "normal error message", + expectedMsg: "normal error message", + }, + { + name: "norma-false-start", + givenErr: "false: normal error message", + expectedMsg: "false: normal error message", + }, + { + name: "norma-false-end", + givenErr: "normal error message, error found in #10 but doesn't follow format", + expectedMsg: "normal error message, error found in #10 but doesn't follow format", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := tryExtractErrorMessage(errors.New(test.givenErr)) + assert.Equal(t, test.expectedMsg, result) + }) + } +} diff --git a/util/jsonutil/stringInt_test.go b/util/jsonutil/stringInt_test.go index e8639c7acee..cd2f3476e46 100644 --- a/util/jsonutil/stringInt_test.go +++ b/util/jsonutil/stringInt_test.go @@ -1,7 +1,6 @@ package jsonutil import ( - "encoding/json" "testing" "github.com/buger/jsonparser" @@ -16,27 +15,27 @@ func TestStringIntUnmarshalJSON(t *testing.T) { t.Run("string", func(t *testing.T) { jsonData := []byte(`{"item_id":"30"}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) assert.Equal(t, 30, int(item.ItemId)) }) t.Run("int", func(t *testing.T) { jsonData := []byte(`{"item_id":30}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) assert.Equal(t, 30, int(item.ItemId)) }) t.Run("empty_id", func(t *testing.T) { jsonData := []byte(`{"item_id": ""}`) var item Item - assert.NoError(t, json.Unmarshal(jsonData, &item)) + assert.NoError(t, UnmarshalValid(jsonData, &item)) }) t.Run("invalid_input", func(t *testing.T) { jsonData := []byte(`{"item_id":true}`) var item Item - err := json.Unmarshal(jsonData, &item) - assert.Equal(t, jsonparser.MalformedValueError, err) + err := UnmarshalValid(jsonData, &item) + assert.EqualError(t, err, "cannot unmarshal jsonutil.Item.ItemId: "+jsonparser.MalformedValueError.Error()) }) } diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go index c02eec158a1..3ca0280d23e 100644 --- a/util/task/ticker_task_test.go +++ b/util/task/ticker_task_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v2/util/task" "github.com/stretchr/testify/assert" ) diff --git a/validate.sh b/validate.sh index b81ade344d2..d263df0ab51 100755 --- a/validate.sh +++ b/validate.sh @@ -17,29 +17,7 @@ while true; do esac done -die() { echo -e "$@" 1>&2 ; exit 1; } - -# Build a list of all the top-level directories in the project. -for DIRECTORY in */ ; do - GOGLOB="$GOGLOB ${DIRECTORY%/}" -done -GOGLOB="${GOGLOB/ docs/}" -GOGLOB="${GOGLOB/ vendor/}" - -# Check that there are no formatting issues -GOFMT_LINES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | wc -l | xargs` -if $AUTOFMT; then - # if there are files with formatting issues, they will be automatically corrected using the gofmt -w command - if [[ $GOFMT_LINES -ne 0 ]]; then - FMT_FILES=`gofmt -s -l $GOGLOB | tr '\\\\' '/' | xargs` - for FILE in $FMT_FILES; do - echo "Running: gofmt -s -w $FILE" - `gofmt -s -w $FILE` - done - fi -else - test $GOFMT_LINES -eq 0 || die "gofmt needs to be run, ${GOFMT_LINES} files have issues. Below is a list of files to review:\n`gofmt -s -l $GOGLOB`" -fi +./scripts/format.sh -f $AUTOFMT # Run the actual tests. Make sure there's enough coverage too, if the flags call for it. if $COVERAGE; then @@ -57,7 +35,6 @@ if [ "$RACE" -ne "0" ]; then fi if $VET; then - COMMAND="go vet" - echo "Running: $COMMAND" - `$COMMAND` + echo "Running go vet check" + go vet -composites=false ./... fi diff --git a/version/xprebidheader.go b/version/xprebidheader.go index fc71bacb1a3..613a76d80b6 100644 --- a/version/xprebidheader.go +++ b/version/xprebidheader.go @@ -5,7 +5,7 @@ import ( "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" ) const xPrebidHeaderVersionPrefix = "pbs-go" diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index 90aef6cb420..db31ea63620 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -1,13 +1,13 @@ package version import ( - "encoding/json" "testing" "github.com/prebid/openrtb/v19/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) func TestBuildXPrebidHeader(t *testing.T) { @@ -134,12 +134,12 @@ func TestBuildXPrebidHeaderForRequest(t *testing.T) { for _, test := range testCases { req := &openrtb2.BidRequest{} if test.requestExt != nil { - reqExt, err := json.Marshal(test.requestExt) + reqExt, err := jsonutil.Marshal(test.requestExt) assert.NoError(t, err, test.description+":err marshalling reqExt") req.Ext = reqExt } if test.requestAppExt != nil { - reqAppExt, err := json.Marshal(test.requestAppExt) + reqAppExt, err := jsonutil.Marshal(test.requestAppExt) assert.NoError(t, err, test.description+":err marshalling reqAppExt") req.App = &openrtb2.App{Ext: reqAppExt} }